A tale of two allocations : make_shared and shared_ptr
make_shared vs shared_ptr
Quick Recap:
Just a quick recap, shared pointers work on the concept of ref count, they maintain a separate control block that stores these count.
The way shared_ptr works is that they maintain -
strong reference count (S) — number of shared_ptr(s) keeping the object alive. The shared object is destroyed (and possibly deallocated) when the last strong ref goes away.
weak reference count (W) — number of active weak_ptr(s) currently observing the object + (S!=0)
Strong and Weak count(s) are typically incremented using an equivalent of atomic::fetch_add with memory_order_relaxed.
Decrementing requires stronger ordering to ensure safe destruction.
Logical model for shared_ptr constructor
If a shared_ptr is constructed from an existing pointer that is not shared_ptr the memory for the control structure has to be allocated.
This Control block is destroyed and deallocated when the last weak ref goes away. A shared_ptr construction approach takes two steps
Logical model for object construction using make_shared
make_shared (or allocate_shared) Allocates the memory for the control structure and the object itself in one single mem block.
The object is then constructed by perfectly forwarding the arguments to its constructor.
Pros make_shared over shared_ptr
Performance: Reduced number of separate allocations
Cache locality: Actions that work with both the count structure and the object itself, will have only half the number of cache misses. (In case cache misses are big issue, we might want to avoid working with single object pointers altogether)
Order of execution and exception safety: (concern pre-C++17)
A possible execution ordering is
1) new Lhs(“foo”))
2) new Rhs(“bar”))
3) std::shared_ptr<Lhs>
4) std::shared_ptr<Rhs>
And one important advantage, especially in cases of preC++17 codes, is the execution safety. So look at this snippet. Foo has this function signature, you make the call like this..now before c++17 there is no restriction of argument resolution so one of the possible resolution may look like this — now what is second step throws..you have a leak, right?
Fix 1: Use make_shared
Fix 2: Code expansion
Pro shared_ptr vs. make_shared
Access to the constructor - make_shared needs access to the constructor it has to call
Lifetime of the object storage (not the object itself) — The second advantage is about the lifetime of the object storage (not the object) this is about the destruction vs deallocation, when the last weak count goes off, then only deallocation would take place. In case of make_shared the single block becomes the bottleneck. For large size objects in association with some long life weak_ptr, this may become problematic.
With shared_ptr, You can also specify a custom deleter, if needed!
Conclusion : Unless good reason, follow this
As a guideline usually, make_shared is more preferable to shared_ptr, but there are cases as stated above where one might need shared_ptr as well.