Understanding Smart Pointers in C++

One of the main responsibility as a programmer is to ensure that your application is not leaking memory. Many OOP languages provide a garbage collector which makes sure no leakage occurs. C++ also provides such mechanism. However, it is not called Garbage Collector, it is called "Smart Pointers".

You may ask, why do I need Smart pointers when I can simply call delete when I no longer need the object? For example:

void foo(){
  //create the object
  MyObject *myObject=new MyObject();

  ...Do stuffs here

  //delete the object
  delete myObject;
}

And you are right, you can simply call a delete when you no longer need the object. However, between the creation of an object to its deletion, an exception may occur and thus never reaching the delete call.

This is where smart pointers come in. The idea is simple:

You give your object to a smart pointer, and they will make sure that your object is deleted once they are out of scope or if you assigned them with a nullptr.

For example, the code below shows a shared_ptr in action. A shared_ptr is a type of smart pointers:

void foo(){
  //create the smart pointer and assign an object to manage
  shared_ptr<MyObject> mySmartObject(new MyObject());

  ...Do stuffs here

  //You don't have to worry about deleting the object. The smart pointer will do so for you once it is out of scope
}

The example below shows how you can release the object by assigning a nullptr to the smart pointer:

void foo(){
  //create the smart pointer and assign an object to manage
  shared_ptr<MyObject> mySmartObject(new MyObject());

  mySmartObject=nullptr; //MyObject destructor will be called, thus releasing the object.

  ...Do other stuffs here
}

Once you give your object to a smart pointer, you can use the pointer as if it was a normal pointer. For example:

void foo(){
  //create the smart pointer and assign an object to manage
  shared_ptr<MyObject> mySmartObject(new MyObject());

  //call a method of your MyObject
  mySmartObject->printTheName();

  ...Do other stuffs here
}

Note: To use smart pointers you need to add the following line of code to your application:

#include <memory>

Types of Smart Pointers

There are three types of Smart Pointers. They are:

  • shared_ptr
  • weak_ptr
  • unique_ptr

shared_ptr

A shared_ptr is a type of a smart pointer. They are declared as follows:

void foo(){
  //create the shared_ptr and assign an object to manage
  shared_ptr<MyObject> mySmartObject(new MyObject());
}

There are two qualities that makes a shared_ptr interesting:

  1. It can share an object with other share pointers.
  2. It increases its count everytime an object is shared. When this count drops to zero, then the object is automatically deleted.

For example, the code below shows how a share_ptr can share an object with another shared_ptr:

void foo(){
//create the shared_ptr and assign an object to manage
shared_ptr<MyObject> mySmartPointer(new MyObject());

//create a second shared_ptr and share the object
shared_ptr<MyObject> anotherSmartObject=mySmartPointer; 
}

In the above example, the count of shared_ptr sharing ownership of MyObject is 2. The only time when the object will be release is when this count drops to 0. Thus, assigning a nullptr to one of the shared_ptr will not release the object because the count would now be 1. To release the object, both shared_ptr have to be set to nullptr.

void foo(){
  //create the shared_ptr and assign an object to manage
  shared_ptr<MyObject> mySmartPointer(new MyObject());

  //create a second shared_ptr and share the object
  shared_ptr<MyObject> anotherSmartObject=mySmartPointer; 

  //count up to now is 2
  //set mySmartPointer to nullptr
  mySmartPointer=nullptr; 

  //count now is 1, thus the object will not be released

  //set anotherSmartObject to nullptr
  anotherSmartObject=nullptr;
  //count is now zero, thus the object will be released

  //...Do other stuffs here
}

weak_ptr

There is another type of smart pointers known as weak_ptr. As opposed to shared_ptr, these pointers only observe the life of the object being managed.

When you declare a weak_ptr, it points to NOTHING. You can point a weak_ptr to an object only by copy or assignment from a shared_ptr or an existing weak_ptr. For example:

void foo(){
  //create the shared_ptr and assign an object to manage
  shared_ptr<MyObject> mySmartPointer(new MyObject());

  //construct a weak pointer from a shared_ptr
  weak_ptr<MyObject> myWeakPointer(mySmartPointer);

  //An empty weak_ptr points to NOTHING
  weak_ptr<MyObject> mySecondWeakPointer;

  //Now it points to something
  mySecondWeakPointer=mySmartPointer;

  //...Do other stuffs here
}

A weak_ptr only observes the managed object. There is very little that you can do with a weak_ptr. You can't dereference the managed object as you would with a shared_ptr.

So what is the purpose of a weak_ptr?

Imagine that you want to call a managed object but you are not sure if the object still exist. This is where a weak_ptr comes in handy. You can test if a managed object is still alive before accessing its members.

The steps to do so is simple. You first get a shared_ptr from the weak_ptr by calling the lock() method on the weak_ptr. This essentially creates a shared_ptr from the weak_ptr. Then you can test if the managed object is alive. For example:

void foo(){
  //create the shared_ptr and assign an object to manage
  shared_ptr<MyObject> mySmartPointer(new MyObject());

  //construct a weak pointer from a shared_ptr
  weak_ptr<MyObject> myWeakPointer(mySmartPointer);

  //...Do other stuffs here
  //...Maybe the shared_ptr was assigned a nullptr but we are not sure

  //test if the managed object is still alive
  //get a shared_ptr from the weak_ptr
  shared_ptr<MyObject> p1=myWeakPointer.lock()

  //test if the managed object still sxist
  if(p1){
  //...Object still alive
  }else{
  //..Nope, the object is long gone my friend.
  }
}

unique_ptr

A unique_ptr is a type of smart pointer that enforces that objects are owned by exactly one unique_ptr. This is in complete contrast with shared_ptr, where an object can be managed by several shared_ptr. This unique ownership is enforced by disallowing copy construction and copy assignment. So you can't copy or assign a unique_ptr to another unique_ptr.

So how do you declare a unique_ptr? Here is an example:

void foo(){
  //create the unique_ptr and assign an object to manage
  unique_ptr<MyObject> p1(new MyObject());

  //Copy construction is not allowed
  unique_ptr<MyObject> p2(p1);

  //Copy assignment is also not allowed
  unique_ptr<MyObject> p3;
  p3=p1;
}

Although copy constructor and copy assignments are not defined in unique_ptr, move constructor and move assignments are defined. Thus, a unique_ptr can transfer ownership of a managed object. After a move construction the newly created unique_ptr owns the object and the original unique_ptr owns nothing.

This comes in handy when a method returns a unique_ptr. Since the returned value of a method is a rvalue, the presence of move construction and move assignment means that we can return a unique_ptr from a method and assign it to another unique_ptr. For example:

unique_ptr<MyObject> createPointer(){
  //create a local unique pointer
  unique_ptr<MyObject> p1(new MyObject());

  return p1; // p1 will surrender ownership
}

void main(){
  unique_ptr<MyObject> a1; //unique_ptr points to nothing
  a1=createPointer(); //now a1 owns the object
}

So there you have it. To avoid memory leaks in your application make sure to use smart pointers. They are very simple to setup and they can be used just like any other pointers you have used before.

PS. Sign up to my newsletter and get development tips

Harold Serrano

Computer Graphics Enthusiast. Currently developing a 3D Game Engine.