I've written earlier this year about smart pointers in C++, today I just would like to write a little more about shared_ptr. Especially about addiction to shared_ptr, and why you should use unique_ptr in C++11.
pre C++11 and boost smart pointers
Before I dive into C++11 and std::shared_ptr, a few words first, where the roots of std::shared_ptr and unique_ptr are. With the usage of smart pointers, boost has been for many years the preferred source for them. As there is no move, you could not put boost::scoped_ptr into an STL Container. An alternative would be to use boost::ptr_container, but often people are lazy, and so std::vector<boost::shared_ptr<T>> is often used. Its not a good or ideal solution, but its used in a fair amount of code in this world. It clearly deals with memory leaks, and before C++11, is a option worth considering for a lot of C++ programmers. Also some people tend to use shared_ptr in their factories and interfaces, especially if inheritance hierarchies are involved. But before I dive too deep into the world before unique_ptr existed, lets move on to C++11.
C++11, std::shared_ptr and std::unique_ptr
C++11 is in this field of memory management for me a game changer. Move-semantics allow now for using unique_ptr in factories, safely storing std::unique_ptr into STL containers, also unique_ptr can fill the role boost::scoped_ptr. Except for the rare case of sharing, you should always prefer unique_ptr now. This has a few advantages:
- the ownership is clearly defined, you but also other users of your code have to think about this (good!)
- a factory giving you a unique_ptr, gives you the access to the allocated object, you still can transfer it into a shared_ptr or any other ownership model.
- you can't concurrently access a unique_ptr, if your thread has a unique_ptr its also the owner of this object.
- you still can share access to a resource via const references, but make sure that changes made by the owner does not affect the objects holding references.
- unique_ptr has a smaller memory footprint, as no control block is allocated.
If you don't have access to C++11, prefer boost with scoped_ptr and ptr_container.
This is what this blog post is actually about, over using shared_ptr. Especially when you move to C++11. I do have to confess, that I had my days of shared_ptr addiction too. shared_ptr, when used with boost in pre C++11 code, it manages very well the allocated resource, and I could focus on something else.
This changed as I visited in 2012 C++Now, Sean Parent gave a very nice keynote. Part of this keynote was about shared_ptr, and that if its misused, its actually a hidden global variable. This thought was new to me, I had never looked at shared_ptr from this angle, but I think, he is right. Especially as more and more code runs in multithreaded ways (CPU, GPU), sharing a heap object/resource across your application might become an issue. Last year Sean Parent has given a very good talk at Going Native, again dealing with pointers and shared_ptr.
But today I think being a potential global variable isn't the only issue with shared_ptr overuse. If shared_ptr is exposed in a public interface, for example in a factory or make function (except std::make_share), it will force you and any other user of this code, to use std or/and boost::shared_ptr. Unlike unique_ptr, shared_ptr can by design not release its ownership, forcing any user to clone the object inside (if possible), if shared_ptr is not useable.
So, when ever you use or see shared_ptr in your code, you should ask whether you really need it, or could replace it either by unique_ptr or scoped_ptr/ptr_container. And of course if you really need to allocate the object with new at the first place.
(My) use cases for shared_ptr
I'd like to give a few examples where shared_ptr can or should be used. First, if you really need to share a resource, think about using a const Type for the shared_ptr:
shared_ptr<const T> const_resource;
Once created, the object can not be changed. Maybe it is a large data set loaded from a database, that now some threads would like to do calculations in parallel. Maybe it is just your config loaded at the beginning of the application.
Also, there is weak_ptr, which allows for storing weak references to a shared_ptr, and only temporarily converting them into a shared_ptr. This works as long as a valid instance of a shared_ptr exists. Also weak_ptr helps to break up cyclic referencing in between shared_ptr instances. So when you need to expose shared_ptr in a public interface, think about preferring weak_ptr.
Join the Meeting C++ patreon community!
This and other posts on Meeting C++ are enabled by my supporters on patreon!