Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

make_shared "evidence" vs default construct

make_shared allocates single block for object and reference counter. So there is an obvious performance benefit using such technique.

I made simple experiment in VS2012 and I was looking for 'evidence':

std::shared_ptr<Test> sp2 = std::make_shared<Test>();
std::shared_ptr<Test> sp(new Test());
// Test is a simple class with int 'm_value' member

when debugging I got in locals view something like this (some lines are deleted)

-   sp2 shared_ptr {m_value=0 }  [make_shared] std::shared_ptr<Test>
+   _Ptr    0x01208dec {m_value=0 } Test *
+   _Rep    0x01208de0 make_shared  std::_Ref_count_base *

-   sp  shared_ptr {m_value=0 } [default] std::shared_ptr<Test>
+   _Ptr    0x01203c50 {m_value=0 } Test *
+   _Rep    0x01208d90 default  std::_Ref_count_base *

It seems that sp2 is allocated in 0x01208de0 (there is a ref counter) and then in 0x01208dec there is an Test object. Locations are very close to each other.

In second version we have 0x01208d90 for ref counter, and 0x01203c50 for object. Those locations are quite distant.

Is this proper output? Do I understand this correctly?

like image 328
fen Avatar asked Feb 02 '13 20:02

fen


3 Answers

If you read cppreference's page for make_shared, they say:

This function allocates memory for the T object and for the shared_ptr's control block with a single memory allocation. In contrast, the declaration std::shared_ptr<T> p(new T(Args...)) performs two memory allocations, which may incur unnecessary overhead.

So this is the intended behaviour and you interpreted it properly.

And of course, it makes sense; how could the shared_ptr control the allocation of an object you already allocated? With make_shared, you leave it in charge of allocating the object, so it can allocate space your object wherever it wants, which is right beside the counter.

Addendum: As Pete Becker noted in the comments, §20.7.2.2.6/6 of the Standard says that implementations are encouraged, but not required, to perform only one allocation. So this behaviour you observed should not be relied upon, though it's safe to say you have nothing to lose and something to gain if you always use make_shared.

like image 189
Seth Carnegie Avatar answered Oct 22 '22 16:10

Seth Carnegie


Yes, the output shown is correct.

In the case of sp2, created through make_shared<>(), there is one block of contiguous memory containing the reference counter and the allocated object. This is why the two addresses are close and this is also one of the main reasons why make_shared<>() exists (to perform only one allocation rather than two).

In the case of sp, instead, you allocate the object separately through new Test() and then construct the shared_ptr object. The constructor of shared_ptr has to issue a new allocation for the reference counter. For this reason the address of the pointed object and the address of the reference counter are distant.

like image 4
Andy Prowl Avatar answered Oct 22 '22 15:10

Andy Prowl


Is this proper output?

Looks like it.

The whole point of std::make_shared is performance - dynamic memory allocations are relatively expensive and doing an extra allocation just to hold the reference counter can be rather wasteful. Therefore, std::make_shared allocates a slab of memory large enough for both the object and the counter, and then just initializes the object (using placement new) in its correct place within that slab.

like image 2
Branko Dimitrijevic Avatar answered Oct 22 '22 15:10

Branko Dimitrijevic