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).

Dmitry Poroh
  • 3,705
  • 20
  • 34
  • 5
    The two are completely orthogonal. `enable_shared_from_this` is relevant when you *already have* a shared pointer. – Kerrek SB Jul 15 '16 at 13:24
  • @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. – Dmitry Poroh Jul 15 '16 at 13:29
  • 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. – Quentin Jul 15 '16 at 13:29
  • 1
    I don't understand the confusion: you make a shared pointer with either `p = make_shared()` or `p = shared_ptr(new T())`. Either way, you can then say `p->shared_from_this()` to get another share. – Kerrek SB Jul 15 '16 at 13:34
  • `enable_shared_from_this` allow to "retrieve" a `shared_ptr` from an instance (owned by `shared_ptr`). – Jarod42 Jul 15 '16 at 13:34
  • 2
    @KerrekSB The question is not about how to retrieve shared pointer. The question about memory allocations optimization. – Dmitry Poroh Jul 15 '16 at 13:43
  • @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(new T)` you have two separate allocations. – Kerrek SB Jul 17 '16 at 23:47

1 Answers1

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.

Jonathan Wakely
  • 166,810
  • 27
  • 341
  • 521
  • Thanks for the perfect explanation! – Dmitry Poroh Jul 15 '16 at 13:49
  • It's quite an interesting question, but the more I think about how it would work the more complications I find. – Jonathan Wakely Jul 15 '16 at 13:51
  • 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). – Dmitry Poroh Jul 15 '16 at 14:00
  • And problem became more complex if take in account overloaded new/delete :) – Dmitry Poroh Jul 15 '16 at 14:02
  • 2
    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(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). – Jonathan Wakely Jul 15 '16 at 15:17