First, you should use

std::make_shared
(most of the time) to create shared pointers. It is optimized to perform only one allocation for both the reference count / control block and the object it holds.

It is also safer to use

std::make_shared
in the presence of exceptions:

some_function(std::shared_ptr<T>(new T), std::shared_ptr<T>(new T));

Prior to C++17, one of the possible orders of events could have been:
1)

new T

2)
new T

3)
std::shared_ptr<T>(...)

4)
std::shared_ptr<T>(...)

So in the worse case the code could leak an instance of

T
if the second allocation in step #2 failed. That is not the case with C++17. It has more stringent requirements on the order of function parameter evaluation. C++17 dictates that each parameter must be evaluated completely before the next one is handled. So the new order of events becomes:
1) 1st
new T

2) 1st
std::shared_ptr<T>(...)

3) 2nd
new T

4) 2nd
std::shared_ptr<T>(...)

or:
1) 2nd
new T

2) 2nd
std::shared_ptr<T>(...)

3) 1st
new T

4) 1st
std::shared_ptr<T>(...)

Shared pointers can also be used with arrays by providing a custom deleter, like this:

auto p = std::shared_ptr<T>(new T[N], [](T* ptr) { delete [] ptr; });

Unfortunately you can not use

std::make_shared
for that, and it is much easier to use arrays with
std::unique_ptr
since
std::shared_ptr
does not provide an overloaded
operator []
, but about that in my next post 😉

Disadvantages of using

std::make_shared
:

std::make_shared
can only construct instances of
T
using
T
‘s public constructors. If you need to create an instance using private or protected constructor you have to do it from within a member or static-member function of
T
. It is not possible to declare
std::make_shared
to be a friend of
T
(technically you can declare it to be a friend, but during invocation of
std::make_shared
it will fail a
static_assert
inside
std::is_constructible
type trait class, so for all intents and purposes friendship is not possible here).

std::weak_ptr
makes things even more complicated:

Finally, let’s take a look at when instances of objects held by

std::shared_ptr
are destroyed and their memory released. Under normal circumstances the destructor of
T
held by a shared pointer will be called when the last shared pointer is destroyed, and the memory where
T
lived will be released.

That is not the case if the shared pointer of

T
was constructed using
std::make_shared
and
std::weak_ptr
was made to point at it.

In order to function properly

std::weak_ptr
must hold a reference to the shared pointer’s control block so that it can: 1) answer the call to
use_count()
and 2) return a
nullptr
when
lock()
is called on it after the last shared pointer went out of scope. If the shared pointer’s control block and the instance of
T
lived in the same memory block, that memory can not be freed until all shared and weak pointers referencing it go away. Now, the destructor of
T
will be called when the last shared pointer goes away, but the memory will linger until the remaining weak pointers are gone.

I didn’t mention anything about passing shared pointers as function arguments or return values… but that’s a topic about higher level design of object ownership and lifetime; maybe I’ll write about it after I cover unique pointers.

4 Replies to “In-depth look at C++ shared pointers”

  1. Nice Topic. I was just watching a video of CppCon and “Nicolai” talked about the pitfall of std::make_shared.

Leave a Reply to Martin VorbrodtCancel reply