I've read at many places that when using make_shared<T>
to create a shared_ptr<T>
, its control block contains a block of storage large enough to hold a T
, and then the object is constructed inside the storage with placement new. Something like this:
template<typename T>
struct shared_ptr_control_block {
std::atomic<long> count;
std::atomic<long> weak_count;
std::aligned_storage_t<sizeof (T), alignof (T)> storage;
};
But I'm a bit confused why we can't just have a member variable with type T
instead? Why create the raw storage then use placement new? Can't it be combined in one step with a normal object of type T
?
Use unique_ptr when if you want to have single ownership(Exclusive) of resource. Only one unique_ptr can point to one resource. Since there can be one unique_ptr for single resource its not possible to copy one unique_ptr to another. Use shared_ptr if you want to share ownership of resource .
What is the technical problem with std::shared_ptr::unique() that is the reason for its deprecation in C++17? this function is deprecated as of C++17 because use_count is only an approximation in multi-threaded environment.
std::shared_ptr is a smart pointer that retains shared ownership of an object through a pointer. Several shared_ptr objects may own the same object.
The difference is that std::make_shared performs one heap-allocation, whereas calling the std::shared_ptr constructor performs two.
It's to allow lifetime management.
The control block is not destroyed until weak_count
is zero. The storage
object is destroyed as soon as count
reaches zero. That means you need to directly call the destructor of storage
when the count reaches zero, and not in the destructor of the control block.
To prevent the destructor of the control block calling the destructor of storage
, the actual type of storage
cannot be T
.
If we only had the strong reference count, then a T
would be fine (and much simpler).
In actual fact, the implementation is a bit more complex than this. Remember that a shared_ptr can be constructed by allocating a T
with new
, and then constructing the shared_ptr
from that. Thus the actual control block looks more like:
template<typename T>
struct shared_ptr_control_block {
std::atomic<long> count;
std::atomic<long> weak_count;
T* ptr;
};
and what make_shared
allocates is:
template<typename T>
struct both {
shared_ptr_control_block cb;
std::aligned_storage_t<sizeof (T), alignof (T)> storage;
};
And cb.p
is set to the address of storage
. Allocating the both
structure in make_shared
means that we get a single memory allocation, rather than two (and memory allocations are expensive).
Note: I have simplified: There has to be a way for the shared_ptr destructor to know whether the control block is part of both
(in which case the memory cannot be released until done), or not (in which case it can be freed earlier). This could be a simple bool flag (in which case the control block is bigger), or by using some spare bits in a pointer (which is not portable - but the standard library implementation doesn't have to be portable). The implementation can be even more complex to avoid storing the pointer at all in the make_shared
case.
As weak pointers may outlive the stored object, the lifetime of the control block may have to exceed the lifetime of the stored object. If the managed object would be a member variable it could only get destroyed when the control block gets destroyed (or the destructor would be called two times).
The fact that the storage stays allocated even after the object itself is destructed can actually be a disadvantage of make_shared
in memory constraint systems (although I don't know if this is something encountered in practice).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With