new and make_shared for shared pointers

7.9k views Asked by At

I came across this post and one of the answers by @kerek SB states

std::shared_ptr<Object> p1 = std::make_shared<Object>("foo");
std::shared_ptr<Object> p2(new Object("foo"));

In your code, the second variable is just a naked pointer, not a shared pointer at all.

Now on the meat. make_shared is (in practice) more efficient, because it allocates the reference control block together with the actual object in one single dynamic allocation. By contrast, the constructor for shared_ptr that takes a naked object pointer must allocate another dynamic variable for the reference count. The trade-off is that make_shared (or its cousin allocate_shared) does not allow you to specify a custom deleter, since the allocation is performed by the allocator.

(This does not affect the construction of the object itself. From Object's perspective there is no difference between the two versions. What's more efficient is the shared pointer itself, not the managed object.)

Now I have two questions regarding this post and would appreciate it if someone could clarify this

  1. Why is the second one not a shared pointer ? Will that not increment a reference count

  2. How does make_shared only make one memory allocation and new makes two thus making make_shared more efficent ?

A little clarification on this would be appreciated.

4

There are 4 answers

0
Felix Glas On BEST ANSWER
  1. The code referred to as the second variable is in fact this (taken from OP's code):

    auto ptr_res2(new Object("new"));
    

    This does not create a std::shared_ptr, it creates a pointer to Object.

  2. When creating a std::shared_ptr using its constructor that takes a naked pointer, you must pass a pointer to already allocated memory (e.g. allocated using new). This means that the memory for the object has already been allocated when creating the std::shared_ptr object itself. The std::shared_ptr needs to allocate memory for its own workings, like e.g. a reference counter. So there are 2 allocations: the one using new passed to the ctor of std::shared_ptr and the one required when constructing the std::shared_ptr itself.

    Object* ptr = new Object{"Foo"}; // Allocation 1 (object).
    std::shared_ptr<Object> p1{ptr}; // Allocation 2 (internal counters).
    

    The helper function std::make_shared uses only 1 allocation as you pass it the arguments needed to construct the object, not a pointer to the object itself. std::make_shared can then allocate memory once that holds both the object and the ref counter.

    auto p2 = std::make_shared<Object>{"Foo"} // Allocation 1 (object & counter).
    
0
Jiří Pospíšil On

Why is the second one not a shared pointer ? Will that not increment a reference count

I believe the quote refers to the original poster's code, which claims to create a smart pointer but in fact does not do that. ptr_res2 is just a regular pointer.

cout << "Create smart_ptr using new..." << endl;
auto ptr_res2(new Object("new"));
cout << "Create smart_ptr using new: done." << endl;

How does make_shared only make one memory allocation and new makes two thus making make_shared more efficent

make_shared needs to allocate a slot for the counter and the object itself. It's possible to allocate the memory in one go and then use part of it for the counter and the rest for the object.

Note that this will also mean that the counter and the object itself are right next to each other in memory thus improving data locality.

0
jacheng On
  1. The second one is still a shared pointer. It is calling the constructor for shared_ptr:

http://www.cplusplus.com/reference/memory/shared_ptr/shared_ptr/

  1. However, using make_shared is more efficient because it is only doing one allocation rather than 2 allocations. Remember, a shared_ptr needs space on the heap for Object and also the manager to keep track of the number of shared and weak pointers. If you are using the constructor, you are allocating space for Object, then passing the pointer in to the constructor which needs to allocate space for the manager. Instead, if you use make_shared, it allocates one chunk of memory that stores Object and the manager. Because allocation is relatively expensive, one allocation is better than two.
7
Barry On

In that question, the "second variable" referred to this line:

auto ptr_res2(new Object("new")); // this creates an Object*

Not this one:

std::shared_ptr<Object> p2(new Object("foo")); // this creates a shared_ptr<Object>

The best explanation for why make_shared is more efficient with one allocation is to compare images. Here is what std_shared_ptr<Object>(new Object) looks like:

enter image description here

The shared_ptr has a Widget*, but it's not in the same memory block as the ref counters since they were allocated separately. The Widget* was passed in, and the ref counting block was allocated internally, which is why the Widget is in a separate memory space.

On the other hand, here is what it looks like with one allocation:

enter image description here

(I'm stealing both pictures from Herb Sutter). We still need a Widget and a ref counting block, but instead the whole memory block is grabbed in a single call to new / malloc, just of sufficient size, so the Widget and ref counting block end up contiguous in memory.