On Smart Pointers and Memory Management
How modern is ‘modern’?
Yesterday, I came across a bug1 in one of the snippets we used for graphs in the Data Structures and Algorithms course.
Under very specific conditions, the program crashed with a generic
bad_alloc error while everything was logically sound.
After some hours debugging I noticed that I had overlooked circular references using pointers.
In a graph represented by a vector of
vertices—each with a neighbourhood of references to other
vertices— we may stumble upon circular references: Node $A$ points to Node $B$ and Node $B$ points to Node $A$, which points to Node $B$ which points to Node $A$… and so on.
This behaviour generates memory leaks, which you may not be aware (as it was my case) until you get caught in the aforementioned loop. If the loop can be avoided by design, it’s great. If not, then we need new tools to deal with that. Tools I did not know I had at my disposal.
To solve this, Modern C++ uses Smart Pointers, which help ensure that programs are free of memory and resource leaks, as stated in Microsoft Documentation’s entry on the subject. These pointers create a single smart pointer instance which is automatically deleted after going out of scope.
There are basically two types of smart pointers: those which are unique references (
unique_ptr) and those which share the same resource with other pointers. On the graph problem we are using the latter, since a vertex may be referenced as a neighbour on multiple locations.
These shared pointers (
shared_ptr) create a single smart pointer instance which updates an internal counter to keep track of multiple references instead of generating another pointer to the same memory address.
Shared pointers include a subclass which is referred to as a weak pointer (
weak_ptr)— pointer instances which are only used to check a memory address without increasing the reference count.
Extremely useful to break out from those pesky circular references.
Weak pointers cannot be dereferenced.
Instead, we lock on the weak pointer and get an atomic
shared_ptr instance for normal use via dereference or accessing its methods using the arrow (
Another interesting thing about smart pointers is automatic deletion. Since this process is now handled by the compiler, the way in which we use pointers is bound to change.
Smart pointers only make sense if the object can be destroyed using a
delete, so they can only be used on objects generated via the
new keyword and not on objects already existing in the call stack.
When an object is created, a shared pointer may be generated pointing to it. If additional references emerge, we instantiate the original pointer and that’s it. If we perhaps need to use a one-time only pointer, we can use weak pointers, lock on the actual reference of the originally shared pointer, use it and done.
Smart pointers are available since C++11, that is, since 2011. They are incredibly useful, and make sense even to developers coming from more modern languages in which memory management is automatic. Moreover, this is now an industry standard. Let’s try to include them as a revised topic on pointers in our courses and C++ books, shall we?