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.

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

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

  • 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