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_sharedin 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
Tif 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_sharedfor that, and it is much easier to use arrays with
std::unique_ptrsince
std::shared_ptrdoes not provide an overloaded
operator [], but about that in my next post 😉
Disadvantages of using std::make_shared
:
std::make_sharedcan only construct instances of
Tusing
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_sharedto be a friend of
T(technically you can declare it to be a friend, but during invocation of
std::make_sharedit will fail a
static_assertinside
std::is_constructibletype 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_ptrare destroyed and their memory released. Under normal circumstances the destructor of
Theld by a shared pointer will be called when the last shared pointer is destroyed, and the memory where
Tlived will be released.
That is not the case if the shared pointer of
Twas constructed using
std::make_sharedand
std::weak_ptrwas made to point at it.
In order to function properly
std::weak_ptrmust 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
nullptrwhen
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
Tlived 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
Twill 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.
Nice Topic. I was just watching a video of CppCon and “Nicolai” talked about the pitfall of std::make_shared.
Thanks for the heads up! I will check it out!
I have learn alot from this article really thank you