Search

15.6 — std::shared_ptr

Unlike std::unique_ptr, which is designed to singly own and manage a resource, std::shared_ptr is meant to solve the case where you need multiple smart pointers co-owning a resource.

This means that it is fine to have multiple std::shared_ptr pointing to the same resource. Internally, std::shared_ptr keeps track of how many std::shared_ptr are sharing the resource. As long as at least one std::shared_ptr is pointing to the resource, the resource will not be deallocated, even if individual std::shared_ptr are destroyed. As soon as the last std::shared_ptr managing the resource goes out of scope (or is reassigned to point at something else), the resource will be deallocated.

Like std::unique_ptr, std::shared_ptr lives in the <memory> header.

This prints:

Resource acquired
Killing one shared pointer
Killing another shared pointer
Resource destroyed

In the above code, we create a dynamic Resource object, and set a std::shared_ptr named ptr1 to manage it. Inside the nested block, we use copy initialization (which is allowed with std::shared_ptr, since the resource can be shared) to create a second std::shared_ptr (ptr2) that points to the same Resource. When ptr2 goes out of scope, the Resource is not deallocated, because ptr1 is still pointing at the Resource. When ptr1 goes out of scope, ptr1 notices there are no more std::shared_ptr managing the Resource, so it deallocates the Resource.

Note that we created a second shared pointer from the first shared pointer (using copy initialization). This is important. Consider the following similar program:

This program prints:

Resource acquired
Killing one shared pointer
Resource destroyed
Killing another shared pointer
Resource destroyed

and then crashes (at least on the author’s machine).

The difference here is that we created two std::shared_ptr independently from each other. As a consequence, even though they’re both pointing to the same Resource, they aren’t aware of each other. When ptr2 goes out of scope, it thinks it’s the only owner of the Resource, and deallocates it. When ptr1 later goes out of the scope, it thinks the same thing, and tries to delete the Resource again. Then bad things happen.

Fortunately, this is easily avoided by using copy assignment or copy initialization when you need multiple shared pointers pointing to the same Resource.

Rule: Always make a copy of an existing std::shared_ptr if you need more than one std::shared_ptr pointing to the same resource.

std::make_shared

Much like std::make_unique() can be used to create a std::unique_ptr in C++14, std::make_shared() can (and should) be used to make a std::shared_ptr. std::make_shared() is available in C++11.

Here’s our original example, using std::make_shared():

The reasons for using std::make_shared() are the same as std::make_unique() -- std::make_shared() is simpler and safer (there’s no way to directly create two std::shared_ptr pointing to the same resource using this method). However, std::make_shared() is also more performant than not using it. The reasons for this lie in the way that std::shared_ptr keeps track of how many pointers are pointing at a given resource.

Digging into std::shared_ptr

Unlike std::unique_ptr, which uses a single pointer internally, std::shared_ptr uses two pointers internally. One pointer points at the resource being managed. The other points at a “control block”, which is a dynamically allocated object that tracks of a bunch of stuff, including how many std::shared_ptr are pointing at the resource. When a std::shared_ptr is created via a std::shared_ptr constructor, the memory for the managed object (which is usually passed in) and control block (which the constructor creates) are allocated separately. However, when using std::make_shared(), this can be optimized into a single memory allocation, which leads to better performance.

This also explains why independently creating two std::shared_ptr pointed to the same resource gets us into trouble. Each std::shared_ptr will have one pointer pointing at the resource. However, each std::shared_ptr will independently allocate its own control block, which will indicate that it is the only pointer owning that resource. Thus, when that std::shared_ptr goes out of scope, it will deallocate the resource, not realizing there are other std::shared_ptr also trying to manage that resource.

However, when a std::shared_ptr is cloned using copy assignment, the data in the control block can be appropriately updated to indicate that there are now additional std::shared_ptr co-managing the resource.

Shared pointers can be created from unique pointers

A std::unique_ptr can be converted into a std::shared_ptr via a special std::shared_ptr constructor that accepts a std::unique_ptr r-value. The contents of the std::unique_ptr will be moved to the std::shared_ptr.

However, std::shared_ptr can not be safely converted to a std::unique_ptr. This means that if you’re creating a function that is going to return a smart pointer, you’re better off returning a std::unique_ptr and assigning it to a std::shared_ptr if and when that’s appropriate.

The perils of std::shared_ptr

std::shared_ptr has some of the same challenges as std::unique_ptr -- if the std::shared_ptr is not properly disposed of (either because it was dynamically allocated and never deleted, or it was part of an object that was dynamically allocated and never deleted) then the resource it is managing won’t be deallocated either. With std::unique_ptr, you only have to worry about one smart pointer being properly disposed of. With std::shared_ptr, you have to worry about them all. If any of the std::shared_ptr managing a resource are not properly destroyed, the resource will not be deallocated properly.

Conclusion

std::shared_ptr is designed for the case where you need multiple smart pointers co-managing the same resource. The resource will be deallocated when the last std::shared_ptr managing the resource is destroyed.

15.7 -- Circular dependency issues with std::shared_ptr, and std::weak_ptr
Index
15.5 -- std::unique_ptr

11 comments to 15.6 — std::shared_ptr

  • Sheng

    Hi Alex,
    On line "Note that we created a second shared pointer from the first shared pointer using copy assignment. This is important. Consider the following similar program:"
    right before the second code block, do you mean copy constructor instead?

    Thanks

  • Bobix

    Hi Alex,
      I have some confusion regarding the 2 pointers(1. Pointer pointing to the actual managed object, 2. Pointer pointing to the control block) held by the std::shared_ptr. While referring to the http://en.cppreference.com/w/cpp/memory/shared_ptr , i could see the below description under the ‘Implementation notes’ section
    In a typical implementation, std::shared_ptr holds only two pointers:
       1. the stored pointer (one returned by get());
       2. a pointer to control block.
    The control block is a dynamically-allocated object that holds:
       1. either a pointer to the managed object or the managed object itself;
       2. the deleter (type-erased);
       3. the allocator (type-erased);
       4. the number of shared_ptrs that own the managed object;
       5. the number of weak_ptrs that refer to the managed object.

    And further down it is mentioned that ‘The pointer held by the shared_ptr directly is the one returned by get(), while the pointer/object held by the control block is the one that will be deleted when the number of shared owners reaches zero. These pointers are not necessarily equal.’

    My confusion is regarding the pointer inside the control block. Why is it needed if the std::shared_ptr object already holds 2 pointers (one to point to the actual managed object and other to point to the control block). Basically i am confused about the memory layout of the shared_ptr.

    It would be great if you could help me out here a bit.

  • john

    I think smart pointers are another great example of how abstraction makes our life easier by hiding the "dirty details" that goes on behind the scenes. Your introduction to the OOP in chapter 8 really gave me a lot to think about πŸ™‚

  • john

    Is shared pointer somehow connected to the reference counting? I’ve only heared the term before but don’t know exactly what it is.

    • Alex

      Yes. Reference counting is the technique of “keeping a numeric counter of how many references there are to some object”. Every time a new reference is set to the object, you increment the counter. Every time a reference is cleared, you decrement the counter. When the counter reaches 0, you know that nobody is using the object any more.

  • Maxpro

    think line 13 on 1st and 3rd code example have typo saying unique_ptr instead of shared.

    thanks for the tutorials, please consider doing ones covering the basics of futures and promises.

Leave a Comment

Put C++ code inside [code][/code] tags to use the syntax highlighter