What is the usefulness of `enable_shared_from_this`?

40

Solution 1

It enables you to get a valid shared_ptr instance to this, when all you have is this. Without it, you would have no way of getting a shared_ptr to this, unless you already had one as a member. This example from the boost documentation for enable_shared_from_this:

class Y: public enable_shared_from_this<Y>
{
public:

    shared_ptr<Y> f()
    {
        return shared_from_this();
    }
}

int main()
{
    shared_ptr<Y> p(new Y);
    shared_ptr<Y> q = p->f();
    assert(p == q);
    assert(!(p < q || q < p)); // p and q must share ownership
}

The method f() returns a valid shared_ptr, even though it had no member instance. Note that you cannot simply do this:

class Y: public enable_shared_from_this<Y>
{
public:

    shared_ptr<Y> f()
    {
        return shared_ptr<Y>(this);
    }
}

The shared pointer that this returned will have a different reference count from the "proper" one, and one of them will end up losing and holding a dangling reference when the object is deleted.

enable_shared_from_this has become part of C++ 11 standard. You can also get it from there as well as from boost.

Solution 2

from Dr Dobbs article on weak pointers, I think this example is easier to understand (source: http://drdobbs.com/cpp/184402026):

...code like this won't work correctly:

int *ip = new int;
shared_ptr<int> sp1(ip);
shared_ptr<int> sp2(ip);

Neither of the two shared_ptr objects knows about the other, so both will try to release the resource when they are destroyed. That usually leads to problems.

Similarly, if a member function needs a shared_ptr object that owns the object that it's being called on, it can't just create an object on the fly:

struct S
{
  shared_ptr<S> dangerous()
  {
     return shared_ptr<S>(this);   // don't do this!
  }
};

int main()
{
   shared_ptr<S> sp1(new S);
   shared_ptr<S> sp2 = sp1->dangerous();
   return 0;
}

This code has the same problem as the earlier example, although in a more subtle form. When it is constructed, the shared_ptr object sp1 owns the newly allocated resource. The code inside the member function S::dangerous doesn't know about that shared_ptr object, so the shared_ptr object that it returns is distinct from sp1. Copying the new shared_ptr object to sp2 doesn't help; when sp2 goes out of scope, it will release the resource, and when sp1 goes out of scope, it will release the resource again.

The way to avoid this problem is to use the class template enable_shared_from_this. The template takes one template type argument, which is the name of the class that defines the managed resource. That class must, in turn, be derived publicly from the template; like this:

struct S : enable_shared_from_this<S>
{
  shared_ptr<S> not_dangerous()
  {
    return shared_from_this();
  }
};

int main()
{
   shared_ptr<S> sp1(new S);
   shared_ptr<S> sp2 = sp1->not_dangerous();
   return 0;
}

When you do this, keep in mind that the object on which you call shared_from_this must be owned by a shared_ptr object. This won't work:

int main()
{
   S *p = new S;
   shared_ptr<S> sp2 = p->not_dangerous();     // don't do this
}

Solution 3

Here's my explanation, from a nuts and bolts perspective (top answer didn't 'click' with me). *Note that this is the result of investigating the source for shared_ptr and enable_shared_from_this that comes with Visual Studio 2012. Perhaps other compilers implement enable_shared_from_this differently...*

enable_shared_from_this<T> adds a private weak_ptr<T> instance to T which holds the 'one true reference count' for the instance of T.

So, when you first create a shared_ptr<T> onto a new T*, that T*'s internal weak_ptr gets initialized with a refcount of 1. The new shared_ptr basically backs onto this weak_ptr.

T can then, in its methods, call shared_from_this to obtain an instance of shared_ptr<T> that backs onto the same internally stored reference count. This way, you always have one place where T*'s ref-count is stored rather than having multiple shared_ptr instances that don't know about each other, and each think they are the shared_ptr that is in charge of ref-counting T and deleting it when their ref-count reaches zero.

Solution 4

There is one case where I find enable_shared_from_this extremely useful: Thread safety when using asynchronous callback.

Imagine class Client has a member of type AsynchronousPeriodicTimer:

struct AsynchronousPeriodicTimer
{
    // call this periodically on some thread...
    void SetCallback(std::function<void(void)> callback); 
    void ClearCallback(); // clears the callback
}

struct Client
{
    Client(std::shared_ptr< AsynchronousPeriodicTimer> timer) 
        : _timer(timer)

    {
        _timer->SetCallback(
            [this]
            () 
            {
                assert(this); // what if 'this' is already dead because ~Client() has been called?
                std::cout << ++_counter << '\n';
            }
            );
    }
    ~Client()
    {
        // clearing the callback is not in sync with the timer, and can actually occur while the callback code is running
        _timer->ClearCallback();
    }
    int _counter = 0;
    std::shared_ptr< AsynchronousPeriodicTimer> _timer;
}

int main()
{
    auto timer = std::make_shared<AsynchronousPeriodicTimer>();
    {
        auto client = std::make_shared<Client>(timer);
        // .. some code    
        // client dies here, there is a race between the client callback and the client destructor           
    }
}

The client class subscribes a callback function to the periodic timer. Once the client object goes out of scope, there is a race condition between the callback and the destructor. The callback may be invoked with a dangling pointer!

The solution: use enable_shared_from_this:

struct Client : std::enable_shared_from_this<Client>
{
Client(std::shared_ptr< AsynchronousPeriodicTimer> timer) 
    : _timer(timer)

    {

    }

    void Init()
    {
        auto captured_self = weak_from_this(); // weak_ptr to avoid cyclic references with shared_ptr

        _timer->SetCallback(
        [captured_self]
        () 
        {
            if (auto self = captured_self.lock())
            {
                // 'this' is guaranteed to be non-nullptr. we managed to promote captured_self to a shared_ptr           
                std::cout << ++self->_counter << '\n';
            }

        }
        );
    }
    ~Client()
    {
        // the destructor cannot be called while the callback is running. shared_ptr guarantees this
        _timer->ClearCallback();
    
    }
    int _counter = 0;
    std::shared_ptr< AsynchronousPeriodicTimer> _timer;
}

The Init method is separated from the constructor since the initialization process of enable_shared_from_this is not finalized until the constructor exits. Hence the extra method. It is generally unsafe to subscribe an asynchronous callback from within a constructor since the callback may access uninitialized fields.

Solution 5

Note that using a boost::intrusive_ptr does not suffer from this problem. This is often a more convenient way to get around this issue.

Share:
40
John Smith
Author by

John Smith

Updated on July 08, 2022

Comments

  • John Smith
    John Smith almost 2 years

    I have the following String (based on a CSV):

    str = '234,23,"sdf,23", "sdf", 23-APR-21, "sd",,'
    

    I only want to get the element at the 5th position. So normally I just do str.split(",")[4] => '23-APR-21'

    But now I get returned str.split(",")[4] => '"sdf"'

    Since applying just "," on the escaped part of the CSV returns already two parts. "sdf,23" => sdf and 23

    How would you solve this without using a special CSV parser?

  • Ankit Roy
    Ankit Roy about 15 years
    +1. The key point is that the "obvious" technique of just returning shared_ptr<Y>(this) is broken, because this winds up creating multiple distinct shared_ptr objects with separate reference counts. For this reason you must never create more than one shared_ptr from the same raw pointer.
  • goertzenator
    goertzenator about 11 years
    Thanks, this illustrates the problem being solved better than the currently accepted answer.
  • cdunn2001
    cdunn2001 about 11 years
    Yes, but enable_shared_from_this allows you to work with an API which specifically accepts shared_ptr<>. In my opinion, such an API is usually Doing It Wrong (as it's better to let something higher in the stack own the memory) but if you're forced to work with such an API, this is a good option.
  • Arun
    Arun about 9 years
    +1: Good answer. As an aside, instead of shared_ptr<S> sp1(new S); it may be preferred to use shared_ptr<S> sp1 = make_shared<S>();, see for example stackoverflow.com/questions/18301511/…
  • AnorZaken
    AnorZaken almost 8 years
    I'm pretty sure the last line should read shared_ptr<S> sp2 = p->not_dangerous(); because the pitfall here is that you must create a shared_ptr the normal way before you call shared_from_this() the first time! This is really easy to get wrong! Before C++17 it is UB to call shared_from_this() before exactly one shared_ptr has been created the normal way: auto sptr = std::make_shared<S>(); or shared_ptr<S> sptr(new S());. Thankfully from C++17 onwards doing so will throw.
  • AnorZaken
    AnorZaken almost 8 years
    BAD Example: S* s = new S(); shared_ptr<S> ptr = s->not_dangerous(); <-- It is permitted to call shared_from_this only on a previously shared object, i.e. on an object managed by std::shared_ptr<T>. Otherwise the behavior is undefined (until C++17)std::bad_weak_ptr is thrown (by the shared_ptr constructor from a default-constructed weak_this) (since C++17).. So the reality is that it should be called always_dangerous(), because you need the knowledge of if it has been shared already or not.
  • AnorZaken
    AnorZaken almost 8 years
    This is correct, and the really important part is So, when you first create... because that is a requirement (as you say the weak_ptr isn't initialized until you pass the objects pointer into a shared_ptr ctor!) and this requirement is where things can go horribly wrong if you are not careful. If you create no shared_ptr before calling shared_from_this you get UB - likewise if you create more than one shared_ptr you get UB too. You have to somehow make sure you create a shared_ptr exactly once.
  • AnorZaken
    AnorZaken almost 8 years
    In other words the whole idea of enable_shared_from_thisis brittle to begin with since the point is to be able to get a shared_ptr<T> from a T*, but in reality when you get a pointer T* t it is generally not safe to assume anything about it already being shared or not, and making the wrong guess is UB.
  • underscore_d
    underscore_d over 7 years
    @AnorZaken Good point. It would've been useful if you had submitted an edit request to make that fix. I've just done so. The other useful thing would've been for the poster not to choose subjective, context-sensitive method names!
  • Matthew
    Matthew over 6 years
    It should be noted that in C++11 and later, it is perfectly valid to use a std::shared_ptr constructor on a raw pointer if it inherits from std::enable_shared_from_this. I don' t know if Boost's semantics were updated to support this.
  • SexyBeast
    SexyBeast over 6 years
    This is a brilliant answer, absolutely brilliant!
  • curiousguy
    curiousguy over 6 years
    "internal weak_ptr gets initialized with a refcount of 1" weak ptr to T are non owning smart ptr to T. A weak ptr is a owning smart ref to enough information to make a owning ptr that is a "copy" of other owning ptr. A weak ptr has no ref count. It has access to a ref count, like all owning ref.
  • curiousguy
    curiousguy over 6 years
    This will only work if these objects are always managed by a shared_ptr. You might want to change the interface to make sure it's the case.
  • mchiasson
    mchiasson over 6 years
    You are absolutely correct @curiousguy. This goes without saying. I also like typedef-ing all of my shared_ptr to improve readability when defining my public APIs. For example, instead of std::shared_ptr<Node> getParent const(), I would normally expose it as NodePtr getParent const() instead. If you absolutely need access to the internal raw pointer (best example: dealing with a C library), there's std::shared_ptr<T>::get for that, which I hate mentioning because I've this raw pointer accessor used too many times for the wrong reason.
  • Thorbjørn Lindeijer
    Thorbjørn Lindeijer about 6 years
    @MatthewHolder Do you have a quote for this? On cppreference.com I read "Constructing a std::shared_ptr for an object that is already managed by another std::shared_ptr will not consult the internally stored weak reference and thus will lead to undefined behavior." (en.cppreference.com/w/cpp/memory/enable_shared_from_this)
  • Dan M.
    Dan M. about 6 years
    Why can't you just do shared_ptr<Y> q = p?
  • HankTheTank
    HankTheTank over 5 years
    @DanM. AFAIK you can. I don't get the use case wither
  • Hatted Rooster
    Hatted Rooster over 5 years
    @DanM. You can, that's why this sample is not very useful. There are definitely use cases for it though. When there's no q and you need a p from inside the class.
  • 1800 INFORMATION
    1800 INFORMATION over 5 years
    @DanM. yes you can do that because p and q are both shared_ptr. enable_shared_from_this is required when you are inside the class, e.g. in the method f() that I had above. It is a simplified example, but it shows the essentials
  • Sergei
    Sergei over 5 years
    Better to stay within the standard as much as you can.
  • Matthew
    Matthew over 5 years
    @ThorbjørnLindeijer, you are right, it's C++17 and later. Some implementations did follow the C++16 semantics before it was released. The proper handling for C++11 to C++14 should be to use std::make_shared<T>.
  • jcxz
    jcxz almost 5 years
    So is this basically the same as boost::intrusive_ptr ? What is the difference ?
  • Scylardor
    Scylardor almost 4 years
    In this specific example, I don't understand what is the added value of using enable_shared_from_this here, since the Client clears the timer callback in its destructor ?
  • Elad Maimoni
    Elad Maimoni almost 4 years
    @Scylardor imagine that during the time the timer callback is running, the desturctor is invoked in the main thread. The callback may be access destroyed 'this'. The actual clearing of the callback is neither atomic nor in sync with the timer.
  • Scylardor
    Scylardor almost 4 years
    Oh ok, thank you for clearing that up. I forgot the multithreading implications of this. It makes sense now. Great example !
  • cyb70289
    cyb70289 over 3 years
    I would vote this as the best answer. It clearly addresses the question: WHY is enable_shared_from_this useful? Other answers only try to explain what enable_shared_from_this does.
  • Elad Maimoni
    Elad Maimoni over 3 years
    @cyb70289 note that I just fixed a small mistake. Glad it helped.
  • ed9w2in6
    ed9w2in6 about 3 years
    Thanks, this explains the technique well, but still don't quite understand why we need this in real sceanrio, do you have a actual example?
  • clay
    clay over 2 years
    In this example,is it really useful to capture the client’s weak_ptr? Weak_ptr will not increase the reference count. I think the client’s destructor will still be called before the scope disappears.
  • Elad Maimoni
    Elad Maimoni over 2 years
    @clay you only need to extend the lifetime of the client object while the callback is accessing its internal fields. If you capture a shared_ptr, you will extend the client lifetime for the duration of the callback subscription. Also, you will create a cyclic reference which shared_ptr cannot handle.