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.

std::shared_ptr and arrays

In C++14 and earlier, std::shared_ptr does not have proper support for managing arrays, and should not be used to manage a C-style array. As of C++17, std::shared_ptr does have support for arrays. However, as of C++17, std::make_shared is still lacking proper support for arrays, and should not be used to create shared arrays. This will likely be addressed in C++20.

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

52 comments to 15.6 — std::shared_ptr

  • Alek

    can you  elaborate this please ?what do you mean by one allocation ? how can both pointers share one memory address ?or is it that it constructs an aggregate type like struct for both control block and our resoruce ? ``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.``

    thx in advance!

    • nascardriver

      `std::make_shared` uses _placement-new_.

      It first allocates N bytes of memory without creating an object.
      Then it uses placement-new to create the control block (If required) and another placement-new to create the object. placement-new doesn't allocate any memory, it creates an object at a predefined address.

      Without `std::make_shared`, you'd
      1. allocate memory for your object and create the object (Both using a single `new` call)
      2. have `std::make_shared` allocate memory for- and create the control block.
      = 2 allocations

  • Anthony

    Hi,

    Couple of questions:

    1) Should one ever use a smart pointer to an object allocated on the stack rather than the heap? I.e. is there ever a need?
      
    2) In order to create as efficient code as possible, how does one declare/initialize a std::shared_ptr member variable in the constructor initialization list with std::make_shared?

    Thanks!

    -- Anthony

    • nascardriver

      1) That could be useful if the object on the stack isn't self-cleaning, eg. a type from a C library. I've never seen this done in practice.

      2) Nothing special here

      • Anthony

        Thanks nascardriver.

        That m_pointer would have had to have been declared beforehand with something like..

        .. does this still mean that the memory allocation for pointer/control block/managed resource is optimised?

        • nascardriver

          The declaration in your line 2 doesn't perform any allocations. Member variables are only initialized once (Like all variables). The initialization happens in the constructor initializer list. If you use `std::make_shared`, the 2 allocations (data+control block) are probably optimized into a single allocation.

  • hellmet

    Say I want to ensure I the resource in shared_ptr count goes all the way to zero at the end of my program. How can I do that?

    I can't obviously check that the count is zero, since by the time it's zero, the shared_ptr is also hopefully and probably being destroyed (because shared_ptr.use_count() == 0 means that something weird is happening or make_shared wasn't used, I'm guessing). The closest I can get is to check if at the end of the expected lifetime a.k.a scope of the shared_ptr, it's use_count() == 1. This way, at the end of the scope, the shared_ptr destroys along with it the shared_ptr, and the resource it holds, correct?

    Is there any better way?

    • nascardriver

      A `std::weak_ptr` can share a resource with a `std::shared_ptr` without owning it, so you can check if the use count is 0 at some point. That only works if you know the point at which the resource should have been freed.
      If you don't know how long the resource will live for, you can use use valgrind to check for memory leaks. Since a `std::shared_ptr` that doesn't die will leak memory, valgrind will catch it.

    • hellmet

      I have a case where a resource (say R1) created is used in creating other resources (say R2, R3, R4), and querying their properties (i.e., R2.some_property is dependent on R1.some_property). I was focused on actually getting something working, so my code currently has unnecessary parameters; I mean to say, auto result = R2.some_property(const Type_R1&) {/* code using R1.some_property to return a result*/}.

      I'm at a point where I want to clean up my code, make it more organized. I thought of 2 ideas. One, use shared_ptr<R1> in the construction of R2, R3, R4 so that their properties can be queried easily, without redundant function parameters. But I read that shared_ptr has an access overhead, and I might have to query some of these properties in a tight render loop, so I thought of storing a reference to R1 in R2, R3, R4. It is risky, but I can ensure that lifetimes of R2, R3, R4 will never outlive R1.

      I am not using threads yet, but I plan to do so soon. Reading from many threads is obviously safe (as long as reading causes no side-effects), and shared_ptr guarantees that only the reference counting is thread-safe. So, as long as I'm only reading across threads, shared_ptr or a reference makes no difference, other than in performance. In case I need to modify a property of R1, I need to put it in a mutex lock, and after that, again, shared_ptr and a reference both seem to have the same usage aspects.

      What do you recommend I do? Also, any misconceptions I have in my reasoning?

      P.S: Would be great to have way to write inline code, instead of only a 'block' of code (code tags push code to a new paragraph).

      • nascardriver

        If `R1` is guaranteed to outlive the others, there's no need for a smart pointer. Go with references.

        • hellmet

          That actually makes my life a lot easier, as the objects I'm passing around need custom deleters. When do I use shared_ptr then? When the lifetime is not deterministic? Also, this affects move semantics right? A reference had to be bound to an object instance, so moves don't work anymore. Also, is there a way to update the reference in R2, R3, R4 to a new instance of R1? Clearly, trying to do R2.R1 = some_other_R1_instance will change the actual R1 too.

          What do you suggest I do?

          • nascardriver

            > When do I use shared_ptr then?
            When multiple pointers own a resource, but there is not one individual owner. In your case, you have an owner (At least it sounds like it), so you can use a reference or `std::unique_ptr`.

            > moves don't work anymore
            You can move objects with reference members, but it doesn't affect the moved-from reference. It simply copies the reference (Not the data), like a shallow pointer copy.

            > is there a way to update the reference
            `std::reference_wrapper` can update its target. I'm not certain about the cost, I don't think it adds overhead (After compiler optimizations).

            • hellmet

              Wow... I didn't ... how is that possible! That's so cool!

              This messes with how I thought about references for so long (as aliases)! Need to read up on how they work!

              Thanks for the info!

  • Anthony

    I have a class inside which I want to declare/define a std::shared_ptr as a member variable, but I don't want to assign it a value immediately because the object `MyObj` that it'll be managing doesn't exist yet, so I do:

    Later, I want to assign it a `MyObj`. So I do:

    This appears to work, but upon termination of the program, I'm getting the message:

    I reckon this is because I'm not properly creating one shared_ptr from another, and therefore the program can't keep track of the number of shared_ptr's. What do I do instead? Thanks :)

    • nascardriver

      When `p` dies, it tries to delete `mo`, which has already died, because it has automatic storage duration, and wasn't dynamically allocated, that's 2 issues.

      What you need to do is allocate a new object, but leave ownership handling to the `std::shared_ptr`.

      Now, `p` manages the newly created object, which won't die on its own. It gets freed when all `std::shared_ptr`s that were referencing it die (In this case, only `p`).
      Don't use `std::make_shared` with `reset`. `reset` expects a pointer that is not yet owned by any other smart pointer.

      • Anthony

        Thank you, nascardriver!

        • Anthony

          One more thing regarding style, which has been aggravating me recently... Now, I realise that, strictly speaking, there's no correct answer to this, but what do you do regarding spacing in brace initialisers?:

          int a{};
          int b{1};
          float c{1.0);
          MyObj mo = new MyObj{ 1, 2 };

          Would that be about right?

          • nascardriver

            There's no right or wrong. I put spaces inside the braces (If there's a value), but not outside.

  • Mrio

    I wanted to know if std::shared_ptr could be moved and used similar to std::unique_ptr,thus I try the same example as the guide:

    BUT, the output of the program is just:

    "Ending program"

    I was expecting a compilator error or even a crash but I dont know why the resource constructor is not even called. Any ideas?

  • Alexandra

    Hi,

    I have a question related to std::shared_ptr.

    Can you tell me please why I can still call print() method after I destroyed the Resource object by using reset()?

    #include <iostream>
    #include <memory>

    class Resource
    {
    public:
        Resource() { std::cout << "Resource acquired\n"; }
        ~Resource() { std::cout << "Resource destroyed\n"; }
        void print() { std::cout << "Yey!!!!\n"; }
    };

    int main()
    {
        auto ptr1 = std::make_shared<Resource>();
        ptr1->print();

        ptr1.reset();
        ptr1->print();

        return 0;
    }

    The output of executing this is:

    Resource acquired
    Yey!!!!
    Resource destroyed
    Yey!!!!

    Thank you!

    • This is undefined behavior.
      Why it's probably working for you: The functions aren't bound to an object, ie. they're the same for every instance of `Resource` and don't access any members. The member functions don't die with the object, they live as long as the program lives. Since they're independent of the object, you can call them even if there is not valid object.
      Again, it's UB, don't do it.

  • Abhijit

    You mentioned
    "In C++14 and earlier, std::shared_ptr does not have proper support for managing arrays, and should not be used to manage a C-style array"
    If we still have to use, what is the alternative?

  • is using vector/string also is better than to using shared_ptr for dynamic arrays and strings ?, also vector/string do not have support for move semantics if i understand correctly, thus in case dealing with r values a vector/string won't steal rather make a deep copy which is inefficient, how can i choose which is better to use in array context ? i mean if an array is going to be shared then i should use shared_ptr, if not then vector ?

  • Jon

    To make sure I've got it, say you have 5 shared_ptr's all co-managing the same resource and you want to create a 6th, it doesn't matter which of the original 5 you clone using copy assignment because they all share the same control block?

    Or do they still all have their own control blocks, but with identical data in each?

  • hailinh0708

    In the topic, you said:
    "As of C++17, std::share_ptr does have support for arrays."
    std::share_ptr should be std::shared_ptr.

  • Azad

    Hello Alex,

    1) Can you pls show a best code for shared_ptr implementation ?

    2) why incrementing reference counter in default constructor here? Default means shared_Ptr still doesn’t point to anything ? Is it right implementation?

    SP() : pData(0), reference(0)

    https://www.codeproject.com/Articles/15351/%2FArticles%2F15351%2FImplementing-a-simple-smart-pointer-in-c

    But here no increment here:

    https://www.acodersjourney.com/implementing-smart-pointer-using-reference-counting/

    Which one is right?

    3) why reference counter is decremented and deleted data in assignment operator before incrementing? ? Any example?

    Shared_ptr<T> p1 (new T())

    Shared_ptr<T> p2 = p1  // here counter should be increased to 2. When it can be decremented?//

    Thanks in advance.

  • Azad

    Hello Alex,

    How to implement shared_ptr? Any help? Thanks

  • DC

    i am confused about smart pointers in the context of passing by reference/address? Could you please explain this? Should we be using smart pointers when passing by reference/address? what are the advantages?

    • Alex

      I just updated the lesson on std::unique_ptr to help address this. While you can pass smart pointers by address or reference, instead it's better to pass the resource being managed (rather than the smart pointer itself) by pointer or reference. The called function shouldn't need to know or care how the callee is managing the lifetime of the resource.

  • Mohsen

    Hi Alex. Thanks in advance for replying all of questions.
    Here's another one :-) .
    I always use "using namespace std" on top to avoid typing std::... .
    In above example, when i used make_shared, my text editor put a red line under cout and says "cout is ambiguous". although it does compile and run properly that, was strange for me.
    When i added std:: before "cout", red line disappeared.
    Can you explain what was that?

    • Alex

      You were getting some sort of naming collision on the name "cout". When you added the std:: prefix, the compiler could tell that you meant std::cout as opposed to some other cout that you'd apparently defined somewhere.

  • 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 all code inside code tags: [code]your code here[/code]