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