Search

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. Ut. 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.

Conclusion

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
Index
15.6 -- std::shared_ptr

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

  • 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.

  • 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 C++ code inside [code][/code] tags to use the syntax highlighter