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