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();