Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why 'std::make_shared' is always using the global memory allocation even with class overloaded new/delete operators?

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 from std::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.

like image 347
Patrick Hodoul Avatar asked Sep 13 '19 19:09

Patrick Hodoul


People also ask

Why is the Count of a shared_ptr stored on the heap?

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.

Why is the storage size of a shared pointer larger than sizeof (T)?

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.

What happens to the pointee object when a shared_ptr is destroyed?

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.

What is the difference between make_shared and shared_ptr in C++?

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.


1 Answers

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
like image 60
NathanOliver Avatar answered Sep 18 '22 22:09

NathanOliver