top of page

Smart Pointers in C++11/C++14/C++17

Updated: Dec 4, 2018



What are the smart pointers? Are they really “smart”? How different are they to regular pointers and why should you use them? These are obvious questions one would have when you first get into the land of smart pointers! Let’s delve into a quick view of the smart land in this blog. This applies to C++11/C++14/C++17.


Smart pointers aren’t really new, they have been modeled in boost like open source libraries. In fact, boost being a battleground for testing new tools and weapons of next C++ standard, they did make an appearance there first. It was a hit in the first run in the battleground and with C++11, they do make an official entry into the standard C++ library. You need <memory> C++ header to make them available in your program.


So what are smart pointers?


Smart pointers are one special variety of Resource-Acquisition-Is-Initialization (RAII) classes, and yeah, they are classes. Technically speaking, they are class templates, of course. The job of smart pointers is simple – they take the ownership of the pointer you add to them. Once initialized, they act like regular pointers so they can replace all raw pointer usage with the similar syntax*.


Essentially, smart pointers take ownership of pointer you hand them out and then track its use and destroy pointed to resource when smart pointer itself goes out of scope.


[* Technically speaking this is not 100% true. Some variants of smart pointers don’t support overloaded ‘*’ and/or -> operators.]


What are all smart pointers available?


From C++ 11, we have four smart points available.

  1. auto_ptr

  2. unique_ptr

  3. shared_ptr

  4. weak_ptr

auto_ptr’s are really deprecated with C++11, they have too many issues w.r.t usage interface that using them over rest what you have available with C++11 (and onwards) is to speak the truth, silly. So for this discussion, we will not go over them. Keep in mind you are not losing on the choices, unique_ptr do the job what auto_ptr did, but with the much more flexible interface and without losing any usefulness.


Here’s short program showing all of the smart pointers and typical use.


Few quick things to notice:

  1. unique_ptr’s can’t be copy constructed so can’t be copied. They can be, however, move-constructed and so moved.

  2. unique_ptr’s take exclusive ownership of the pointer. They are a perfect fit for places where you would use auto_ptr in C++98.

  3. shared_ptr’s as the name suggests, share ownership of the pointed resource. Resource pointed to is deleted only when last shared_ptr pointing to it goes out of scope.

  4. weak_ptr’s are born only in the valid context of shared_ptr’s. weak_ptr’s can, however, live longer than shared_ptr’s.

  5. unique_ptr’s take exactly same size as a raw pointer does so they are super optimal in use.

  6. shared_ptr and weak_ptr’s take more than raw pointer would take. This is due to the fact that they maintain control block in the dynamically allocated memory.

  7. make_unique and make_shared functions are more explicit in their intent than explicitly adding a resource to those respective pointer constructor using ‘new’ operator. There’s more to it though (see below), why this form of functions should be used.


Now you may be wondering when do you exactly use unique_ptr and when do you use other two? It’s quite simple actually. So simple thumb rule is wherever you need (or you have) exclusive ownership of the pointer, you use unique_ptr. Most times that is true so that means most times, you would also go with the unique_ptr. Some example scenario’s when you would use unique_ptr are:

  1. You create (and eventually want to delete) some dynamically allocated resources in the current code block (e.g. function)

  2. Bridge classes (one good example based learning for them can be done from here)

  3. Return from factory functions

  4. Etc.

shared_ptr’s are more suited for cases where you need to pass a smart pointer from one place to the other and pointer may live in different contexts and timeline of those parallel contexts is indeterministic. Simplest such use case would be you want to pass a smart pointer from place of its creation to some other function. shared_ptr’s can also be used where unique_ptr’s can do the job but it would be overkill to blindly use that way, besides, keep in mind they are heavier in memory use than unique_ptr’s.


When do you use weak_ptr’s?


This really is a special case. weak_ptr can be created from shared_ptr’s and they, in turn, can also produce back shared_ptr (in fact you have to produce that when you want to use weak_ptr for actual resource access they point to). So weak_ptr’s are useful to return from the main function where you want to retain control over a resource which is dynamically allocated and have same time callers be able to a pointer to it (but free callers from sharing the responsibility to reference count). A simple example of this would be when your factory function is allocating memory for the object and this memory or object is returned from the pre-allocated pool.


Why should I use make_xxx forms of the functions?


Sure, you don't want them then you can always do something like this:


void DoSomeProcessing(int priority, Widget* w); // Declaration

int GetPriority(); // Another declaration


DoSomeProcessing(GetPriority(), new Widget()); // Not exception safe

DoSomeProcessing(GetPriority(), unique_ptr<Widget>(new Widget())); // Not exception safe

DoSomeProcessing(GetPriority(), make_unique<Widget>()); // Exception safe


An exception could arise above when Widget is allocated with ‘new’ operator or when GetPriority() is called. C++ does not guarantee the order in which above parameters would be initialized while calling the function DoSomeProcessing. So in the first call, if GetPriority does exception but Widget is already allocated then it is leaked! The second call shares the same fate! Yes, it might not come obvious but the compiler could decide there to first call ‘new Widget’, then call GetPriority() and then lastly constructor for the unique_ptr. So if GetPriority does exception then we still leak Widget resource amount of memory in the second call, too!


But look closely on the third call, even if the same sequence happens during initialization of the parameters for DoSomeProcessing, this time what is passed is still unique_ptr<Widget> but it is constructed using function so ‘new Widget’ part is part of the make_uqniue function so it either happens before or after GetPriority(). If it happens before, we have fully constructed unique_ptr so exception stack unwinding would make sure Widget allocated is cleanly released (through unique_ptr<Widget> destructor).


A leak can be avoided in the second call above by declaring and initializing unique_ptr<Widget> before the call to DoSomeProcessing, but again, that is more typing to do and more things to remember, which is one more reason to use make_xxx forms. One other advantage of make_shared is they initialize control block for reference counts and actual pointed to resource together so it will only do single 'new' call for memory allocation, while it would be typically two ‘new ’ calls in regular shared pointer constructor.


Summary


Smart pointers are indeed smart and reduce code when “smartly” used. They are special cases of RAII classes. They also make it easy to pass pointers across places or functions without losing pointed to a resource or leaking them. Various forms of smart pointers exist, each has its own unique case, when one fits more better than the other.


Weak and shared pointers take more size than actual raw pointer while unique pointers take equal size as raw pointers. make_xxx forms to initialize smart pointers should be used whenever you can to avoid losing resources with ‘new <class>’ usage.

182 views0 comments

Comments

Rated 0 out of 5 stars.
No ratings yet

Add a rating
bottom of page