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?
If you read cppreference's page for make_shared
, they say:
This function allocates memory for the
T
object and for theshared_ptr
's control block with a single memory allocation. In contrast, the declarationstd::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
.
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.
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.
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