15.7 — Circular dependency issues with std::shared_ptr, and std::weak_ptr

In the previous lesson, we saw how std::shared_ptr allowed us to have multiple smart pointers co-owning the same resource. However, in certain cases, this can become problematic. Consider the following case, where the shared pointers in two separate objects each point at the other object:

In the above example, we dynamically allocate two Persons, “Lucy” and “Ricky” using make_shared() (to ensure lucy and ricky are destroyed at the end of main()). Then we partner them up. This sets the std::shared_ptr inside “Lucy” to point at “Ricky”, and the std::shared_ptr inside “Ricky” to point at “Lucy”. Shared pointers are meant to be shared, so it’s fine that both the lucy shared pointer and Rick’s m_partner shared pointer both point at “Lucy” (and vice-versa).

However, this program doesn’t execute as expected:

Lucy created
Ricky created
Lucy is now partnered with Ricky

And that’s it. No deallocations took place. Uh. oh. What happened?

After partnerUp() is called, the ricky shared pointer goes out of scope. When that happens, ricky checks if there are any other shared pointers co-own the Person “Ricky”. There are (the shared pointer inside Person “Lucy”). Because of this, it doesn’t deallocate “Ricky” (if it did, then “Lucy” would end up with a hanging pointer). Next lucy goes out of scope, and the same thing happens. The shared pointer lucy checks if there are any other shared pointers co-owning the Person “Lucy”. There are (the shared pointer inside Person “Ricky”). So it doesn’t deallocate Lucy.

Then the program ends -- and neither Person “Lucy” or “Ricky” have been deallocated! Essentially, “Lucy” ends up keeping “Ricky” from being destroyed, and “Ricky” ends up keeping “Lucy” from being destroyed.

It turns out that this can happen any time shared pointers form a circular reference.

Circular references

A Circular reference (also called a cyclical reference or a cycle) is a series of references where each object references the next, and the last object references back to the first, causing a referential loop. The references do not need to be actual C++ references -- they can be pointers, unique IDs, or any other means of identifying specific objects.

In the context of shared pointers, the references will be pointers.

This is exactly what we see in the case above: “Lucy” points at “Ricky”, and “Ricky” points at “Lucy”. With three pointers, you’d get the same thing when A points at B, B points at C, and C points at A. The practical effect having shared pointers form a cycle is that each object ends up keeping the next object alive -- with the last object keeping the first object alive. Thus, no objects in the series can be deallocated because they all think some other object still needs it!

A reductive case

It turns out, this cyclical reference issue can even happen with a single std::shared_ptr -- a std::shared_ptr referencing the object that contains it is still a cycle (just a reductive one). Although it’s fairly unlikely that this would ever happen in practice, we’ll show you for additional comprehension:

In the above example, when ptr1 goes out of scope, it doesn’t deallocate the Resource because the Resource’s m_ptr is sharing the Resource. Then there’s nobody left to delete the Resource (m_ptr never goes out of scope, so it never gets a chance). Thus, the program prints:

Resource acquired

and that’s it.

So what is std::weak_ptr for anyway?

std::weak_ptr was designed to solve the “cyclical ownership” problem described above. A std::weak_ptr is an observer -- it can observe and access the same object as a std::shared_ptr (or other std::weak_ptrs) but it is not considered an owner. Remember, when a std::shared pointer goes out of scope, it only considers whether other std::shared_ptr are co-owning the object. std::weak_ptr does not count!

Let’s solve our Person-al issue using a std::weak_ptr:

This code behaves properly:

Lucy created
Ricky created
Lucy is now partnered with Ricky
Ricky destroyed
Lucy destroyed

Functionally, it works almost identically to the problematic example. However, now when ricky goes out of scope, it sees that there are no other std::shared_ptr pointing at “Ricky” (the std::weak_ptr from “Lucy” doesn’t count). Therefore, it will deallocate “Ricky”. The same occurs for lucy.

Using std::weak_ptr

The downside of std::weak_ptr is that std::weak_ptr are not directly usable (they have no operator->). To use a std::weak_ptr, you must first convert it into a std::shared_ptr. Then you can use the std::shared_ptr. To convert a std::weak_ptr into a std::shared_ptr, you can use the lock() member function. Here’s the above example, updated to show this off:

This prints:

Lucy created
Ricky created
Lucy is now partnered with Ricky
Ricky's partner is: Lucy
Ricky destroyed
Lucy destroyed

We don’t have to worry about circular dependencies with std::shared_ptr variable “partner” since it’s just a local variable inside the function. It will eventually go out of scope at the end of the function and the reference count will be decremented by 1.


std::shared_ptr can be used when you need multiple smart pointers that can co-own a resource. The resource will be deallocated when the last std::shared_ptr goes out of scope. std::weak_ptr can be used when you want a smart pointer that can see and use a shared resource, but does not participate in the ownership of that resource.

Quiz time

1) Fix the “reductive case” program so that the Resource is properly deallocated.

Show Solution

15.x -- Chapter 15 comprehensive review
15.6 -- std::shared_ptr

31 comments to 15.7 — Circular dependency issues with std::shared_ptr, and std::weak_ptr

  • neil

    """However, now when ricky goes out of scope, it sees that there are no other std::shared_ptr pointing at “Ricky” (the std::weak_ptr from “Lucy” doesn’t count)."""
    Intuitively I would think a going out-of-scope shared_ptr works like "I'm going out of scope let me destroy myself BUT first"->"Decrement the reference counter"->"IF counter drops to zero, destroy the object I'm pointing to"->"Ok now I can self destroy", which means a shared_ptr do not know or care about who else is also pointing to the object, only a counter.
    However that sentence I quoted from you somehow suggests that the shared_ptr somehow "knows" about the difference between other shared_ptr and weak_ptr? I know effectively "weak_ptr doesn't count" is the reason, but does share_ptr "know"? Or is my intuition wrong?

    • Alex

      Under the hood, std::shared_ptr uses reference counting as you suggest. It doesn't have any actual visibility into the other pointers, it just can look at the reference count (which is shared across std::shared_ptrs) and determine how many references there are.

      Put another way, std::shared_ptr x has no idea what std::shared_ptr y is pointing at, nor what std::weak_ptr z is pointing at.

  • MarvinBruh

    Hi Alex,
    Thank you for the tutorials. In this sub-chapter, can't I just use a normal, dumb pointer or reference for the m_partner member of the Person class to reference another Person?

    The m_partner pointer will just refer to the managed object, not own it, so I think it should be fine... until the Person instance it's referring to expires, in which it becomes a dangling pointer.

    What are other downsides in using dumb pointers over weak references in this scenario?

    Also, I think a solution for the dangling pointer would be to have a manager class that handles the deleted people by also setting the partners of the people that refer to them to nullptr (the manager class is friend of Person). What do you think?

  • Will

    Does the getPartner() example works with real women? Asking for a friend 😀

  • Benjamin

    Typo. That one happens all the time to me: just a single colon as scope resolution operator: "std:shared_ptr" in section "So what is std::weak_ptr for anyway?"

  • Mattermonkey

    Is there a good reason for std::weak_ptr not being directly accessible, or is it just an oversight, like std::initialiser_list not having an operator[]?

  • Hashem Omar

    hi Alex..
    i need to make sure i got this right in the last example :
    "We don’t have to worry about circular dependencies with std::shared_ptr variable “partner” since it’s just a local variable inside the function. It will eventually go out of scope at the end of the function and the reference count will be decremented by 1."

    so you are talking here about the ( m_partner.lock() ) right? but what about the shared pointer (partner) itself? i mean the function returned a shared pointer to Lucy and it was assigned to (partner), now we have 2 shared pointers for lucy and one for ricky, what happens then?

    • Alex

      I'm actually talking about the local partner variable in main. At that point, we have 1 shared pointer (partner), and 2 weak pointers (lucy and ricky's m_partner). Local variable partner will go out of scope first. At that point, there are no shared pointers left, so when lucy and ricky go out of scope, there's nothing preventing m_partner from deleting its contents.

      • Trevor29

        Hi Alex
        I may be missing something, but with no explicit inner blocks within your main function, how can you be sure that local variable "partner" will go out of scope before lucy and ricky? Might this be implementation-dependent?

        • Alex

          Local variables are destroyed in reverse order of creation. Since partner was last created, it's first destroyed.

          But that's beside the point, really, as the code still works even if partner was destroyed last. That's the whole point with std::shared_ptr -- the object isn't destroyed until all references to it go out of scope.

  • Martin

    Small spelling mistake:
    "Ut. Oh." -> "Uh. Oh."

  • Ninja

    Hey Alex. So I tried messing around with weak pointers and I found out I can’t dereference them or access anything from it. I managed to solve it by using a getter function called getPartner which returns the weak pointer as a shared pointer. I only got it to work by the code below. For some reason, std::make_shared didn’t work. Also, do I have to cast back to a weak_ptr after I'm done using it?

    This is in main.cpp

    • Alex

      Good question. I've updated the lesson with an additional section showing how to actually USE std::weak_ptrs. In short, you have to convert them to a std::shared_ptr to use them, which you can do via the lock() function. You don't have to cast back to a weak_ptr, as the cast doesn't alter the original weak_ptr, it just created an additional shared_ptr pointing to the same thing.

      • Ninja

        I see. Thanks. So I'm going to make a game engine using OpenGL and I was thinking of using shared pointers for everything. I just have to make sure I don't have them pointing to each other (or else I have to use a weak pointer) but I don't see a case where I'd do that. I'm worried that I might run into a circular dependency issue and I don't know how to debug that or find out that is the case. Is there a way to do that?

        Or should I just stick to normal pointers and do memory deallocation myself? An example where I might have to use it is for a physics class that tests and resolves collisions between all game objects (by going through a list). There will also be a rendering component that will render on screen for the game object. So there will probably be a bunch of shared pointers to game object. Also, I'm worried that the shared pointers might delete itself when I don't want it too. This is my first time making an engine so I'm not sure if I should incorporate smart pointers in general since I'm not that experienced with it. What do you think?

        • Alex

          A few thoughts:
          1) The best way to learn is to try and fail.
          2) Whether you use normal or smart pointers really depends on what you need them for. I'd favor smart pointers for memory allocated inside a function, but for classes, they're less necessary since you have a destructor to clean up. I'd also try and avoid shared altogether pointers if possible (favor unique_ptr). Or even better, avoid pointers altogether and use references or maybe even ids/indices if possible.

  • Jiaan

    Hello Alex.
    What would happen if the resource get deallocated yet I still try to access this via a weak_ptr? Wouldn't this be a dangling pointer?

  • john

    Hi Alex, thanks again for your tutorials, they're really amazing! Soon it will be 10 years since you started this website and it is still very active 🙂 Can you share anything you've learned by making this tutorials over the years? Any plans for the future? Or at least are there any lectures coming soon (so I can pay attention 🙂 )?

    • Alex

      Hard to believe it's been almost 10 years. I've learned so many things over the last 10 years: don't be overambitious. Always be courteous. I've learned a ton about running a website (and how to protect it from malicious attacks). I've also learned a lot about C++ -- the best way to ensure you understand something is to teach it to someone else. They'll inevitably point out things you didn't think of.

      Coming up: A short chapter on some miscellaneous topics that don't really fit in elsewhere (unions, the mutable keyword), and then probably into a discussion on the standard library container classes and data structures. Unfortunately, my time for writing is really limited right now, and that will probably get worse before it gets better due to an impending new addition to my family, so I won't promise it'll happen soon.

  • Jim

    Hello Alex,

    Your tutorials are really good. I really appreciate your efforts. Please include other advance topics.

    Once again thank you very much for such a good tutorial.

    • Alex

      What else would you like to see?

      • Jan Perme

        Reading Jim’s comment, I completely agree with him on his point. If you would also cover multithreading and patterns in C++, using the same systematic approach as in other sections, you would be a complete winner on WWW. Your approach is to the point, no nonsence and that is such a huge added value. I really enjoy it, as well as many others out there. Reading other content (books or online tutorials from other authors), I simply fall asleep reading that stuff -_-. So if you want to keep someone up all night, offer him your learncpp tutorial. I’m sure he won’t be able to stop reading it 😀

  • Billstah

    Hi, Alex.
    Really enjoying the tutorial so far! But just an idea, maybe you could create another appendix just as a general re-cap of all the topics covered on the website, like possibly bunch of quizzes? I dunno, but it's just a thought :).

  • anuj

    When you add the use of alignof, alignas, const_cast, export, extern, mutable, noexcept, reinterpret_cast, thread_local, typeid, typename, union, volatile in your tutorials...

  • abc

    The solution is not in coding style of your site and it miss the include files

Leave a Comment

Put all code inside code tags: [code]your code here[/code]