When using std::make_shared<C>
the class overloaded new/delete operators are not called.
When using std::shared_ptr<C>
, std::unique_ptr<C>
& std::make_unique<C>
the class overloaded new/delete operators are used.
When looking at the documentation it's perfectly correct and well documented.
cppreference explains the behavior:
std::make_shared
uses ::new, so if any special behavior has been set up using a class-specific operator new, it will differ fromstd::shared_ptr<T>(new T(args...))
.
Below is some pseudo-code to better highlight the behavior:
#include <memory>
class C {
public:
void* operator new(size_t size) {
void* p = ::operator new(size);
std::cout << "C::new() -> " << p << "\n";
return p;
}
void operator delete(void* p) {
std::cout << "C::delete() -> " << p << "\n";
::operator delete(p);
}
};
std::shared_ptr<C> ptr = std::make_shared<C>();
From an external point of view, it seems inconsistent and error prone. Overloading class new/delete operators should always be used.
So, what is the rationale of the behavior?
And, where is the C++ specification detailing the std::make_shared
behavior?
Thanks for your help.
This, in turn, means that the count has to be stored somewhere else, on the heap. When a shared_ptr is constructed from an existing pointer that is not another shared_ptr, the memory for the count structure has to be allocated.
The storage is typically larger than sizeof (T) in order to use one allocation for both the control block of the shared pointer and the T object. The std::shared_ptr constructor called by this function enables shared_from_this with a pointer to the newly constructed object of type T.
While the pointee object is destroyed when the last shared_ptr releases its ownership, the ref count structure needs to live on until the last weak_ptr is gone. When we use make_shared this includes the storage for the pointee object.
std::make_shared uses ::new, so if any special behavior has been set up using a class-specific operator new, it will differ from std::shared_ptr<T>(new T(args...)) . std::shared_ptr supports array types (as of C++17), but std::make_shared does not.
So, what is the rational of the behavior?
The reason this is done is because make_shared
doesn't just allocate your object, it also allocates the control block of the shared_ptr
. To make this as efficient as possible, it calls new
once and allocates enough storage for the control block and the object in one go. Otherwise it would have to call new
twice which doubles the allocation overhead.
If you want to use a custom allocator then you need to use std::allocate_shared
and it will use your custom allocator to do a single memory acquisition to create the shared_ptr
.
Another option is to use std::make_unique
to create a unique_ptr
, and then use that to initialize the shared_ptr
. This works because unique_ptr
does not have a control block so std::make_unique
allocates in the form of
unique_ptr<T>(new T(std::forward<Args>(args)...))
That would give you
std::shared_ptr<C> ptr = std::make_unique<C>();
which outputs
C::new() -> 0xf34c20
C::delete() -> 0xf34c20
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