Lesson #4: Smart Pointers

One big change to modern C++ style that comes with C++11 is that you should never need to manually delete (or free) anymore, thanks to the new classes shared_ptr, unique_ptr and weak_ptr.

Note that before C++11, C++ did have one smart pointer class – auto_ptr. This was unsafe and is now deprecated, with unique_ptr replacing it.

To use these classes, you'll need to #include <memory> (and also add using namespace std; or prefix with std::).

unique_ptr

unique_ptr simply holds a pointer, and ensures that the pointer is deleted on destruction. unique_ptr objects cannot be copied.

It thus behaves very much like the now-deprecated auto_ptr behaved – the problem with auto_ptr was that it was aiming to work what unique_ptr does, but unable to do so properly when it was defined, back before C++11 was invented with features like move-constructors, and thus it was unsafe.

As example of how unique_ptr is used is as follows – say we have the following:

struct MyClass {
   MyClass(const char* s);
   void methodA();
};
void someMethod(MyClass* m);

void test() {
   unique_ptr<MyClass> ptr1(new MyClass("obj1"));

   // can use -> (and *) on the unique_ptr just like with a normal pointer
   ptr1->methodA(); 

   // to get a plain pointer from the unique_ptr, we use the get() method
   someMethod(ptr1.get()); 

   // use std::move to transfer ownership to ptr2 - ptr1 now holds no pointer
   unique_ptr<MyClass> ptr2(std::move(ptr1));

   // assign a new pointer to ptr1
   ptr1.reset(new MyClass("obj2"));

   // assign a new pointer to ptr2 - "obj1" will now automatically be deleted
   ptr2.reset("obj3");

   // set ptr1 to contain nothing - "obj2" will now automatically be deleted
   ptr1.reset();

   // "obj3" will automatically deleted at the end of this function, as ptr2 goes out of scope
}

The following parts will describe three useful ways in which unique_ptr is often used …

unique_ptr for class members

A simple use of unique_ptr is for replacing the use of pointers in storing class members. For example, say you have the following class which simply stores a pointer to an int:

class A {
public:
   int* i;

   A():i(new int(0)) { }

   ~A() { 
      if(i) {
         delete i; 
      }
   }

private:
    // we need to explicitly disable value-copying, as it's not safe!
    A(const A&);
    A& operator=(const A&);
};

Using unique_ptr, this class simplifies to:

class B {
public:
   unique_ptr<int> i;

   B():i(new int(0)) { }
};

The destructor is no longer needed, and value-copying will automatically be disabled, as unique_ptr is not copyable. On top of that, this unique_ptr version also gets a move constructor defined automatically, thanks to unique_ptr being moveable (whereas the former would have to additionally define a move constructor to get that).

This code is not only more concise, but by eliminating the need to remember boilerplate code, it also eliminates two dangers which are common causes of memory leaks and memory corruption:

  • forgetting to delete all class-member pointers in the destructor.
  • forgetting to disable value-copying (or to define a suitable copy-constructor + operator= to handle it safely).

Making unique_ptr class members work with Forward Declarations

Say you're declaring ClassA to have a class member variable that is a pointer to ClassB, and you want to store that pointer using a unique_ptr object. And say you don't want to #include ClassB.h in ClassA.h (rather you want to forward declare ClassB there in order to save compilation time etc).

When you try to do this, you may sometimes encounter compilation problems, depending upon the compiler. In which case, to fix this you may need to ensure that the constructor and/or destructor are defined in the corresponding .cpp file, rather than being defined in the .h file or being not defined at all and thus left to default implementations.

Note that you would in any case generally have needed to do exactly this before when using a plain pointer and forward declaration. It's only now that using unique_ptr that you may have been able to avoid writing a destructor and just relied on the default constructor and/or destructor, as in the above example where the destructor was no longer needed).

This same caveat also applies to using shared_ptr for class member variables, although it again depends on the compiler.

unique_ptr for local variables within functions

A second example of the use of unique_ptr is for local variables within functions. Say you have the following function:

void methodA() {
   int* buf = new int[256];

   int result = fillBuf(buf)) 
   if(result == -1) {
      return; 
   }
   printf("Result: %d", result);

   delete[] buf;
}

The above code has a couple of major safety issues:

  • it is forgetting to delete buf when return is called, and thus will leak memory.
  • if any exception is thrown by fillBuf(buf), it will again fail to delete `buf and thus leak memory.

A safer form of this function while still using a plain pointer would thus be something like:

void methodA() {
   int* buf = new int[256];

   try {
      int result = fillBuf(buf)) 
      if(result == -1) {
         delete[] buf;
         return; 
      }
      printf("Result: %d", result);
   }
   catch(...) { 
      delete[] buf;
      throw;
   }
   delete[] buf;
}

This should now be safe, however it's a lot of code to repeat and get right every time.

With unique_ptr, you avoid all this boilerplate – you just do the following, and the code is perfectly safe:

void methodA() {
   unique_ptr<int> buf(new int[256]);

   int result = fillBuf(buf)) 
   if(result == -1) {
      return;
   }
   printf("Result: %d", result);
}

unique_ptr in STL collections

When using STL collections in 'old C++', you have the choice of:

  • storing objects by value, which can add a lot of overhead quickly if the objects are large and thus being copied around.
  • storing objects with pointers, which suddenly adds a whole lot of memory management issues:
    • whenever an item is erased from the collection, the user has to remember to also the object it points to
    • when the collection itself is destroyed, the user has to first remember to iterate the entire collection in order to remove+erase any objects it still contains.

C++11 solves this above conundrum with move constructors (see my post on move semantics). These allow any object which defines cheap move constructors to be stored in collections by value rather than pointer, with little/no performance overhead as before (in fact potentially with performance gains due to less indirection).

unique_ptr also defines such move constructors, thus although it is not copyable, it is moveable, and so the collections still have a way to move it around – transferring the pointer that it points to between unique_ptr objects.

Thus for objects which don't define inexpensive move constructors, unique_ptr provides a safe way to store them in STL collections, with no difference in performance from storing plain pointers, i.e.:

std::vector<unique_ptr<MyClass>> v;

v.push_back(unique_ptr<MyClass>("hello world"));
v.emplace_back(new MyClass("hello world"));

MyClass* m = v.get();

This example also demonstrates the use of the new emplace_back in C++11, reducing a little verbosity otherwise attached with using unique_ptr to wrap the objects in the vector. emplace_back works like push_back, but allows you to simply pass the arguments you would otherwise pass to the constructor for the object which you're storing in the vector, rather than passing the object itself. This method can also be more efficient than using the old push_back, so should be generally preferred.

shared_ptr

shared_ptr functions the same way as unique_ptr – holding a pointer, providing the same basic interface for construction and using the pointer, and ensuring that the pointer is deleted on destruction.

Unlike unique_ptr, it also allows copying of the shared_ptr object to another shared_ptr, and then ensures that the pointer is still guaranteed to always be deleted once (but not before) all shared_ptr objects that were holding it are destroyed (or have released it).

It does this using reference counting – it keeps a shared count of how many shared_ptr objects are holding the same pointer. This reference counting is done using atomic functions and is thus threadsafe.

An example of its use is as follows:

struct MyClass {
   MyClass(const char* s);
   void methodA();
};
void someMethod(MyClass* m);

auto ptr = make_shared<MyClass>("obj1");

ptr->methodA();

someMethod(ptr.get());

shared_ptr<MyClass> anotherPtr = ptr; // now anotherPtr + ptr are both pointing to the "obj1" object

ptr.reset(new MyClass("obj2"); // now ptr switches to pointing to "obj2", but the "obj1" 
                               // object is not deleted as anotherPtr is still holding it

anotherPtr.reset(); // now no shared_ptr object is referencing the "obj1" MyClass*, so it is deleted

// "obj2" will be automically deleted when ptr goes out of scope

Note the use of make_shared in the above – while it is possible to create a shared_ptr using the same syntax as for unique_ptr (passing a pointer to its constructor), make_shared should be always preferred, as it also happens to be more efficient (only requiring one memory allocation rather than two).

shared_ptr and thread-safety

One place where the use of shared_ptr can really shine (and the main case where you would need to use shared_ptr rather than unique_ptr is in multi-threaded applications. When you're not sure which thread will finish needing an object last, you can simply give each thread a shared_ptr that references the object.

However, note here that I said that you give each thread one such object. The shared_ptr class is not thread-safe for the case that two threads try to access the same shared_ptr object concurrently. Rather, thread-safety is ensured as long as each thread has their own shared_ptr objects (which may in turn all share+reference the same pointer).

weak_ptr

A weak_ptr is used to hold a non-owning reference to a pointer that is managed by a shared_ptr (or multiple shared_ptr objects). It must be converted to shared_ptr in order to access the referenced pointer.

weak_ptr models temporary ownership: when an object needs to be accessed only if it exists, and it may be deleted at any time by someone else, weak_ptr is used to track the object, and it is converted to std::shared_ptr to assume temporary ownership. If the original shared_ptr is destroyed at this time, the object's lifetime is extended until the temporary shared_ptr is destroyed as well.

weak_ptr is primarily useful in the rare cases where it is needed in order to break 'circular references' – a problem with the use of reference counting in shared_ptr. An example of this would be a doubly-linked-list containing two nodes – Node A and Node B – here Node A will contain a forward-reference to Node B, and Node B will contain a back-reference to Node A. If these references are shared_ptr objects, then their reference counts will never reach zero, as even once everything else that refers to Node A and Node B is destroyed, they will still be referencing each other.

   auto sp = std::make_shared<string>("hello world");

   std::weak_ptr<string> wp = sp;

   if(auto spt = wp.lock()) { // to be safe, have to be copy into a shared_ptr before usage
      cout << *spt << endl;
   }
   else {
      cout << "sp was deleted already\n";
   }

General Memory Management Guidelines – What to use when?

Method 1: just use shared_ptr everywhere

One simple approach is to use shared_ptr everywhere (together with weak_ptr in the rare case where you have cycles, but most programmers will never encounter this problem).

This has the advantage of being both safe and simple. And this is precisely the approach that the Objective-C language has taken with it's Automatic Reference Counting – in it's latest iteration it automatically adds reference counting to all objects, making this transparent so that the user is still just allocating and using pointers, but internally these are being reference counted. It's also similar to the approach many other languages take with garbage collection.

The argument against this

There are however are a couple of issues with this approach:

  • if your code is being used by other code (i.e. if you're creating a library), then if you're requiring shared_ptr pointers to be passed to your functions for example, you're imposing this (your) style of memory management on anybody using your code, which is both not neccessary and not appropriate.
  • shared_ptr does incur a little overhead in terms of the atomic operations used in the reference counting, and in that it has to allocate a little extra memory to store the reference count (which is only really an issue if having say millions of pointers to very small objects).
  • by consistently using unique_ptr, shared_ptr and plain-pointer where appropriate (i.e. the following guidelines), the ownership+memory-management+lifetime of objects is clear to anyone looking at code – at a class or method.
    • i.e. when you use shared_ptr everywhere, then looking at a method that takes a shared_ptr, you have no idea what that method will do with it – whether it will retain it or not.
    • such complex or unclear ownership models tend to lead to difficult to follow couplings of different parts of the application through shared state that may not be easily trackable.
  • C++ gurus also seem to be strongly against this idea, i.e. Bjarne Stroustrop argues that "shared_ptr should be used as a last resort when an object's life-time is uncertain" (mainly it appears for the previous reason – making ownership+lifetime of resources clear)

A matter of personal preference perhaps

You might argue that resource lifetime+ownership is all stuff that one ideally shouldn't need to care about, now that you have shared_ptr and can just use it everywhere. You could also make similar arguments against the Final guideline (see below) of avoiding dynamic memory + pointers whenever possible, arguing that this also requires too much thinking about the performance tradeoffs and understanding of the implementations of the classes you're using – how much data is being copied by the objects' move constructors, and are you sure that they have move constructors defined? And that it's easer to simply not bother with defining move constructors correctly for all your classes, and cleaner and simpler just to use shared_ptr everywhere.

I tend to agree that simply using shared_ptr generally is a pretty safe way to do things for many kinds of projects (but I'd be interested to hear comments from those who disagree). I'd say especially for relative beginners, that you could do much worse than to just use shared_ptr everywhere. Also, basically all the other popular programming languages out there are now happily using either automatic reference counting or garbage collection these days, so using C++ effectively in the same way – by using shared_ptr everywhere – can't be such a horrible idea.

Using shared_ptr everywhere will also hurt C++'s performance a little. And I guess people could argue that if you don't care about performance so much, and don't want to think about object lifecycle and such, then there's many other languages out there for you to use (in fact, pretty much every other language out there).

I don't totally buy this argument either though – using shared_ptr everywhere is really not a significant performance cost most of the time (nowhere near as bad as switching to Ruby for example), and I'd like to see C++ as a good tool for many kinds of programming needs, not only a tool to be used by programmers that like/need to invest extra time in understanding and thinking more about memory managment and object ownership+lifetimes in order to squeeze final couple of % of performance out of their programs.

  • note also that you can avoid unnecessary performance penalties from using shared_ptr by minimizing copying of the shared_ptr objects (as that incurs atomic increments and decrements which are the main overhead)

Method 2: some general guidelines

These general guidelines are a little more complicated than above method of just using shared_ptr. They require some thinking about the concept of 'ownership' of your objects – who is the owner of this object, and is there one owner or many? Generally there should just be one owner, however in some cases ownership will need to be shared (and thus in these cases, but only in these cases,shared_ptr is to used). Plain pointers, when used, should always be perceived as non-owning pointers that you must guarantee never outlive the object they points to.

The following guidelines apply:

  • when an object is allocated, it should generally be immediately assigned to a shared_ptr or unique_ptr that is the 'owner':
    • typically this shared_ptr/unique_ptr should act as the 'owner' of the object
    • plain pointers are thus never used to act as the 'owners' of objects
    • the exception to this immediate assignment rule is things like factory methods that return a plain pointer to the object they create – in this case however, the callee still should generally be immediately assigning this returned object to a shared_ptr / unique_ptr
    • also, note here that some argue that even in such cases, the object should still be immediately assigned to a unique_ptr, which these methods should return, and the object then gets transferred (moved) from this unique_ptr to another unique_ptr / shared_ptr for ownership – this all being done in order to ensure that the object is at all times 'owned' by a smart pointer so that there is no risk of memory leaks in the case of an exception being thrown etc
  • methods should take plain-pointers in their arguments, unless they plan to store a reference to a passed object beyond the life of the method (or pass it to another function that does this and is thus also requiring a shared_ptr), in which case they should take shared_ptr.
  • methods should always return plain-pointers when it is up to the caller to handle memory management of the object, i.e. to assume ownership of it with a shared_ptr/unique_ptr or to pass to someone else to own it. Examples of such methods would be:
    • factory methods to create an instance of some class
    • as mentioned earlier, some people argue that unique_ptr should be used in this case, in order that there is always an owner (in case some exception is thrown at any point and thus there's risk of a memory leak)
  • methods that are accessor methods to an owned object that is a unique_ptr should return a plain pointer. Examples of such methods would be:
    • a singleton getInstance() method.
  • methods that are accessor methods to an owned object that is a shared_ptr should generally return a shared_ptr object (though this should be a rare case I would think).
  • use unique_ptr whenever shared_ptr is not neccessary, that is, whenever shared-ownership is not neccessary. It should relatively rarely be that case that shared-ownership is really needed, but cases where it really can be needed include
    • when shared-ownership is required due to sharing an object between multiple threads.
    • for modeling parent-child relations using shared_ptr together with weak_ptr

Final guideline – simply avoid dynamic memory + pointers whenever possible

With C++11 it is generally preferable to simply avoid dynamic allocation, thus avoiding the consequent need for any corresponding form of memory management.

With the new move constructors in C++11 (see my post on move semantics), most objects' classes should have move constructors defined, and thus objects should generally be moveable cheaply (for example STL classes should now all be), thus such objects:

  • can be stored in containers by value without the need to first wrap them in pointers or smart-pointers to avoid costly copies. Thus for example, instead of vector<unique_ptr<string>> or vector<shared_ptr<string>> or vector<string*>, the simple vector<string> should generally be preferred now.
  • can generally passed to functions by value (rather than by reference or as pointers), without any extra cost
  • can generally be returned by functions by value (when returning a temporary at least) without any extra cost

Discussion

I would love to hear all opinions about whether there's any good arguement against someone preferring to simply use smart_ptr everywhere – it is not what I see the experts recommending, and it is not something I use in my work as it generally involves extremely performance critical code, however it would seem like a relatively safe braindead approach, giving the programmer less to worry about for relatively little performance cost.

Also also any discussion about the 'general guidelines' on when to use unique_ptr vs plain pointer vs shared_ptr is welcome.

9 comments

  1. Chenglin Huang · · Reply

    Nice article!
    But I think
    unique_ptr buf(new int[256]);

    should be

    unique_ptr buf(new int[256]);

  2. Thanks, a good coverage of this and the other main C11 stuff in one place.

    Will be referring back here I am sure.

  3. You post interesting content here. Your website deserves much bigger audience.
    It can go viral if you give it initial boost, i
    know very useful service that can help you, just type in google: svetsern traffic tips

  4. Colin Girling · · Reply

    Having been reading about this subject recently, all experts explain why unique_ptr should be preferred over shared_ptr.
    Stroustrup and Sutter both explain in great detail why this is recommended.

  5. /*Thank you I have been avoiding this for some time. I didn’t know enough to really understand the first example as a rough sketch, so I went ahead and made it compile. Hopefully I didn’t undo anything.
    */

    #include
    #include
    #include

    class MyClass {
    std::string name;
    public:
    MyClass(std::string name);
    void printName();
    };

    MyClass::MyClass(std::string newName ) { name = newName; }

    void MyClass::printName() { std::cout<< "Name: " <<name <<std::endl; }

    int main() {
    std::unique_ptr ptr1(new MyClass( “obj1” ));

    // can use -> (and *) on the unique_ptr just like with a normal pointer
    ptr1->printName();

    // to get a plain pointer from the unique_ptr, we use the get() method
    MyClass *plainPtr;
    plainPtr = ptr1.get();

    // use std::move to transfer ownership to ptr2 – ptr1 now holds no pointer
    std::unique_ptr ptr2(std::move(ptr1));

    // assign a new pointer to ptr1
    ptr1.reset(new MyClass(“obj2”));

    // assign a new pointer to ptr2 – “obj1” will now automatically be deleted
    ptr2.reset(new MyClass(“obj3”));

    // set ptr1 to contain nothing – “obj2” will now automatically be deleted
    ptr1.reset();

    // “obj3” will automatically deleted at the end of this function, as ptr2 goes out of scope

    return 0;
    }

  6. I think the best excuse for not using smart pointers everywhere is if you’ve used raw pointers for many years and they come naturally and you’re not fully versed on C++ 11. I’m one of those people. I’m currently using smart pointers in my latest project, but I find them to be confusing. While they make sense since lifetime is uncertain for my objects and happen to also be the simplest route for me as far as I can tell, I’ve had to take up new syntax. Things like make_shared() are confusing. I’ve had to turn to typedefs more often. And then there’s the weak_ptr termage I’ve had to learn. Smart pointers don’t seem lightweight. I know the memory management for raw pointers isn’t necessarily lightweight, but in most of my history of programming memory management has been the least of my worries.

  7. I think the best excuse for not using smart pointers everywhere is if you’ve used raw pointers for many years and they come naturally and you’re not fully versed on C++ 11. I’m one of those people. I’m currently using smart pointers in my latest project, but I find them to be confusing. While they make sense since lifetime is uncertain for my objects and happen to also be the simplest route for me as far as I can tell, I’ve had to take up new syntax. Things like make_shared() are confusing. The ways you make hte pointers is confusing. I’ve had to turn to typedefs more often. And then there’s the weak_ptr termage I’ve had to learn. Smart pointers don’t seem lightweight to me. I know the memory management for raw pointers isn’t necessarily lightweight, but in most of my history of programming memory management has been the least of my worries. The fact you do have to pay attention to how you use smart pointers (like knowing when to use weak and unique pointers) means they have some overhead too.

    Overall I like hte smart pointers, but I don’t see them as the perfect answer for everything. For example, I’m using raw pointers in places where I know the lifetime and I find it to be much swifter, mostly because I”m used to it. For someone who’s never used raw pointers, I can see why they’d universally use smart pointers or unique pointers and weak pointers in rare cases.

  8. ThomThom · · Reply

    One of the items said to use shared_ptr “for modeling parent-child relations using shared_ptr together with weak_ptr”.

    How do you decide on shared+weak ptr versus using unique+plain pointers? Are there scenarios in parent+child relationship where the owning parent might disappear before the child?

  9. v.push_back(unique_ptr(“hello world”));
    v.emplace_back(new MyClass(“hello world”));

    The first one doesn’t work because unique_ptr doesn’t have copy method whereas the second one works.
    Is it a typo? If it is not a typo, i can’t get it to work. Here is the implementation of my class. Please show me what the correct one should be.

    class MyClass1 {
    public:
    MyClass1(string pstr) : data(pstr) {};
    ~MyClass1() {};

    void print() const {
    cout<<data<<endl;
    };
    string data;
    };

    thanks

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: