As illustrated in the code here, the size of the object returned from make_shared is two pointers.
However, why doesn't make_shared
work like the following (assume T is the type we're making a shared pointer to):
The result of
make_shared
is one pointer in size, which points to of allocated memory of sizesizeof(int) + sizeof(T)
, where the int is a reference count, and this gets incremented and decremented on construction/destruction of the pointers.
unique_ptr
s are only the size of one pointer, so I'm not sure why shared pointer needs two. As far as I can tell, all it needs a reference count, which with make_shared
, can be placed with the object itself.
Also, is there any implementation that is implemented the way I suggest (without having to muck around with intrusive_ptr
s for particular objects)? If not, what is the reason why the implementation I suggest is avoided?
The result of make_shared is one pointer in size, which points to of allocated memory of size sizeof(int) + sizeof(T) , where the int is a reference count, and this gets incremented and decremented on construction/destruction of the pointers.
The statement that uses make_shared is simpler because there's only one function call involved. It's more efficient because the library can make a single allocation for both the object and the smart pointer.
Make shared_ptr. Allocates and constructs an object of type T passing args to its constructor, and returns an object of type shared_ptr<T> that owns and stores a pointer to it (with a use count of 1). This function uses ::new to allocate storage for the object.
As well as this efficiency, using make_shared means that you don't need to deal with new and raw pointers at all, giving better exception safety - there is no possibility of throwing an exception after allocating the object but before assigning it to the smart pointer.
In all implementations I'm aware of, shared_ptr
stores the owned pointer and the reference count in the same memory block. This is contrary to what other answers are saying. Additionally a copy of the pointer will be stored in the shared_ptr
object. N1431 describes the typical memory layout.
It is true that one can build a reference counted pointer with sizeof only one pointer. But std::shared_ptr
contains features that absolutely demand a sizeof two pointers. One of those features is this constructor:
template<class Y> shared_ptr(const shared_ptr<Y>& r, T *p) noexcept; Effects: Constructs a shared_ptr instance that stores p and shares ownership with r. Postconditions: get() == p && use_count() == r.use_count()
One pointer in the shared_ptr
is going to point to the control block owned by r
. This control block is going to contain the owned pointer, which does not have to be p
, and typically isn't p
. The other pointer in the shared_ptr
, the one returned by get()
, is going to be p
.
This is referred to as aliasing support and was introduced in N2351. You may note that shared_ptr
had a sizeof two pointers prior to the introduction of this feature. Prior to the introduction of this feature, one could possibly have implemented shared_ptr
with a sizeof one pointer, but no one did because it was impractical. After N2351, it became impossible.
One of the reasons it was impractical prior to N2351 was because of support for:
shared_ptr<B> p(new A);
Here, p.get()
returns a B*
, and has generally forgotten all about the type A
. The only requirement is that A*
be convertible to B*
. B
may derive from A
using multiple inheritance. And this implies that the value of the pointer itself may change when converting from A
to B
and vice-versa. In this example, shared_ptr<B>
needs to remember two things:
B*
when get()
is called.A*
when it is time to do so. A very nice implementation technique to accomplish this is to store the B*
in the shared_ptr
object, and the A*
within the control block with the reference count.
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