Smart pointers and dynamic arrays

If you want to use smart pointers and arrays, there are different ways of doing it.

std::unique_ptr supports managing dynamic arrays and do not need to write your own deleter:

int main()
{
    int letter{97};
    int size{26};

    unique_ptr<char[]> arr(new char[size]());

    for (int i{}; i != size; ++i)
        arr[i] = static_cast<char>(letter++); // supports subscript

    for (int i{}; i != size; ++i)
        cout << arr[i];
}

You write <char[ ]> notice the empty brackets, to say that it points to an array of char. It will now automatically call delete [] arr when it goes out of scope. It also supports the subscript operator.

While std::shared_ptr does not provide any direct support for managing dynamic arrays, and we have to write our own deleter:

int main()
{
    int letter{97};
    int size{26};

    shared_ptr<char> arr(new char[size](), [](char *p) { delete [] p; });

    for (int i{}; i != size; ++i)
        *(arr.get() + i) = static_cast<char>(letter++);

    for (int i{}; i != size; ++i)
        cout << *(arr.get() + i);
}

Since std::shared_ptr doesn’t have subscript operator and doesn’t support pointer arithmetic, we can use get() to obtain a built in pointer.

Smart pointers and dynamic arrays

How to enable C++14 in Code::Blocks?

To compile your source code using the C++14 flag in Code::Blocks, you first of all need to download and install a compiler that supports C++14 features.

Here’s how you can do it on Windows:

1. Download MinGW from here (particular build) or from official site
2. Extract it to for example: C:\ (result will be C:\MinGW)
3. Open Code::Blocks
4. Go to Settings => Compiler.

CB_1

5. Go to “Toolchain Executables”.
6. In the top field “Compiler’s installation directory”, change the directory to the one where you extracted the compiler. E.g C:\MinGW.
7. Change all the necessary files under “Program Files” to match the files under C:\MinGW\bin:

CB_2

8. Before you hit “OK”, go to the leftmost tab “Compiler settings”.
9. Select “Compiler Flags”.
10. Right click in the list somewhere and select “New Flag”:

CB_3

11. Type in the following:

CB_4

12. Click “OK” and tick the box you just created:
CB_5

13. Specify the debugger path. Go to “Settings” => “Debugger”, click “Defualt” on left hand side and enter the new full path of the executable:

CB_6

And finally, create a new C++ project and test if it’s working:

#include <iostream>
#include <string>
using namespace std;

auto main() -> int
{
    auto add_two([](auto x, auto y){ return x + y; });

    cout << add_two("I"s, "t"s) << " works!" << endl;
}
How to enable C++14 in Code::Blocks?

Smart Pointers

C++11 provides smart pointer types that manage dynamic objects. A smart pointer acts like a regular pointer with an exception that it automatically deletes the object to which it points when it is appropriate to do so.

  • shared_ptr – allows multiple pointers to refer to the same object.
  • unique_ptr – “owns” the object to which it points.
  • weak_ptr – a weak reference to an object managed by shared_ptr. (will not be discussed here)

If you don’t initialize a smart pointer, it will be default initialized with a “nullptr”:

shared_ptr<T> sp;  // Null shared_ptr that can point to objects
unique_ptr<T> up;  // of type T.

Dereferencing a smart pointer, reveals the value of the smart pointer:

int val = *p;

When using a smart pointer in a condition, it is “true” if it points to an object and false otherwise:

if (p)
    // use p
else
    // p does not point to an object.

The safest way to allocate and use dynamic memory is to use a library function named “make_shared”, it constructs an object of type T and wraps it in a “shared_ptr”. It uses the arguments passed to it as the parameters for the constructor of T:

// shared_ptr<T> make_shared( Args&&... args );

// p points to a dynamically allocated 'int'
// holding the value 42
auto p = make_shared<int>(42); 

When there are no more pointers referring to an object the use count becomes 0 and the object will be deleted and the memory will be freed:

{
    // p1 points to a dynamically allocated int
    // holding value 0. The use count is 1.
    auto p1 = make_shared<int>();  
                                  
} // p1 goes out of scope. The memory to which p1 points 
  // is automatically freed.

The use count is incremented when we copy a shared_ptr. For example:
The associated counter is incremented when we use a shared_ptr to initialize another shared_ptr, when we use it as the right hand operand of an assignment, or when we pass it to – or return it from a function by value.

The counter is decremented when we assign a new value to the shared_ptr and when the shared_ptr itself is destroyed, such as when a local shared_ptr goes out of scope. Once the shared_ptr’s counter goes to zero, the shared_ptr automatically frees the object it manages:

auto p = make_shared<int>(42);
auto q = make_shared<int>();

// assigning to q, making it point to a different address
q = p; 

// increase the use count for the object to which p points.
// decrease the use count for the object to which q had pointed.
// the object that r had pointed to has no users, that object is
// automatically freed.

The memory will not be freed if there are other shared_ptr’s pointing to it:

shared_ptr<int> func(int val)
{
    auto p = make_shared<int>(val);
    return p; // reference count is incremented when we return p.
} // function goes out of scope, the memory to which p points is not
  // freed.

A unique_ptr “owns” the object to which it points. Unlike shared_ptr, only one unique_ptr at a time can point to a given object. The object to which the unique_ptr points is destroyed when the unique_ptr itself is destroyed.

Operations on unique_ptr:

// 'u1' uses 'delete' to free it's pointer.
unique_ptr<T> u1;
// 'u2' uses a callable object of type 'D'
// to free it's pointer.
unique_ptr<T, D> u2;

// 'u3' is a null unique_ptr that can
// point to an object of type 'T'
// and uses 'u' which must be of 
// type 'D' to free the pointer
// in place of 'delete'
unique_ptr<T, D> u3(u);

// deletes the object to which 'u'
// points, makes 'u' null:
u = nullptr;

// releases control of the pointer
// 'u' had held. Returns the pointer
// 'u' had held and makes it null.
u.release();

// deletes the object to which 'u' points.
u.reset();

// if the built-in pointer 'q' is
// supplied. Makes 'u' point to 
// that object. Otherwise, 'nullptr'
u.reset(u);
u.reset(nullptr);

In C++14, there is a library function comparable to make_shared which works for unique_ptr’s.
‘make_unique’ works like ‘make_shared’. Before C++14, you had to bind the unique_ptr to a pointer returned by ‘new’:

// before c++14:
unique_ptr<int> p1(new int(42));

// with make_unique:
unique_ptr<int> p1 = make_unique<int>(42);

// with auto:
auto p1 = make_unique<int>(42);

Also to mention, the smart pointer constructor that take pointers, are explicit. Therefore, we can’t implicitly convert a built-in pointer to a smart pointer. We must use direct initialization:

// error; must use direct initialization
shared_ptr<int> p1 = new int(42);
    
// OK: uses direct initialization
shared_ptr<int> p2(new int(42));

Since unique_ptr owns the object to which it points, it does not support copy or assignment:

unique_ptr p1(new string("Dinosaur"));
unique_ptr p2(p1); // error
unique_ptr p3 = p1;// error

Although we can’t copy or assign we can transfer ownership:

unique_ptr<int> p1(new int(42));
// transfers ownership from p1 to p2
// makes p1 null.
unique_ptr<int> p2(p1.release());
unique_ptr<int> p1(new int(42));
unique_ptr<int> p2(new int(1));
unique_ptr<int> p3(new int(5));
    
// transfers ownership from p3
// to p2. makes p3 null.
// p2.reset() deletes memory 
// to which p2 had pointed.
p2.reset(p3.release());

If we don’t use another smart pointer to hold the result from .release(), our program will take over responsibility for freeing that resource:

// Wrong, p1 won't free the memory
// and we've lost the pointer.
p1.release();

// Ok, but we must remember to 
// delete p2.
auto p2 = p1.release();
Smart Pointers