Does enable_shared_from_this and make_shared provide the same optimization
Asked Answered
D

1

13

As I understand make_shared<T>(...) may provide some memory allocation optimization (it may allocate reference counter within same memory block as instance of class T).

Do enable_shared_from_this provides the same optimization? So:

class T : std::enable_shared_from_this<T> {};
...
auto t = std::shared_ptr<T>(new T);

Is the same as:

class T {};
...
auto t = std::make_shared<T>();

If not take in account sizeof(T).

Disorderly answered 15/7, 2016 at 13:19 Comment(7)
The two are completely orthogonal. enable_shared_from_this is relevant when you already have a shared pointer.Mazzard
@KerrekSB I suppose that enable_shared_from_this adds some data to the class that can be used by shared pointer implementation. As for me the simplest way - is to place number of references as part of enable_shared_from_this and this may help to reduce memory allocations in the first case.Disorderly
I'm tempted to say "yes", as otherwise there's no way to retrieve the refcount block from inside the object. But that's just a hunch.Gingery
I don't understand the confusion: you make a shared pointer with either p = make_shared<T>() or p = shared_ptr<T>(new T()). Either way, you can then say p->shared_from_this() to get another share.Mazzard
enable_shared_from_this allow to "retrieve" a shared_ptr from an instance (owned by shared_ptr).Mount
@KerrekSB The question is not about how to retrieve shared pointer. The question about memory allocations optimization.Disorderly
@DmitryPoroh: Yes, and I'm saying that the memory optimization is unrelated to enable_shared_from_this. With make_shared you have a single allocation, and with shared_ptr<T>(new T) you have two separate allocations.Mazzard
S
11

Do enable_shared_from_this provides the same optimization? So:

No. As you can see from the wording in the standard, enable_shared_from_this<T> has a weak_ptr<T> data member. That adds a weak_ptr<T> to the class, which has a pointer to the control block that contains the reference counts. It doesn't contain the reference counts directly. The control block containing the reference counts still exists external to the object.

The control block containing the reference counts must outlive the object, so that other weak_ptr objects that used to refer to the object can still access the control block, to check whether it has expired.

If the control block was inside the object it would be destroyed when the object was destroyed, and it would not be possible for a dangling weak_ptr to safely determine if the object had expired. In theory the memory of the control block could remain allocated and still be used and the reference counts updated, even though the object they were part of was destroyed, but that seems pretty ugly (and it would mean the object would not be destroyed with delete, it would require an explicit destructor call and explicit operator delete call to free the memory).

You also couldn't use the embedded control block if the owning shared_ptr was created with a custom deleter or custom allocator, because the size of those objects would not be known in advance. In such cases you'd still need to allocate an external control block in addition to the one embeded in the enable_shared_from_this<T> base class, wasting even more space.

Storebought answered 15/7, 2016 at 13:35 Comment(5)
Thanks for the perfect explanation!Disorderly
It's quite an interesting question, but the more I think about how it would work the more complications I find.Storebought
I suppose in case of make_shared memory of instance also couldn't be freed until last weak_ptr points to the control block. But in contrast with enable_shared_from_this it could be clearly implemented from C++ language point (don't use destroyed object).Disorderly
And problem became more complex if take in account overloaded new/delete :)Disorderly
Yes, with make_shared the memory is not freed until the last weak reference is dropped. But that's OK, because make_shared controls both the allocation+construction and destruction+deallocation, so can do it all correctly. With shared_ptr<X>(new X) the object should be destroyed with delete, but that would prevent the control block from outliving the object (because the memory would be deallocated as soon as the destructor runs).Storebought

© 2022 - 2024 — McMap. All rights reserved.