What is the usefulness of `enable_shared_from_this`?

Viewed 116334

I ran across enable_shared_from_this while reading the Boost.Asio examples and after reading the documentation I am still lost for how this should correctly be used. Can someone please give me an example and explanation of when using this class makes sense.

6 Answers

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.

There is one particular 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 client's callback and the client's destructor. The callback may be invoked with a dangling pointer!

The solution: using enable_shared_from_this to extend the object lifetime for the duration of the callback invocation.

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 mechanism of enable_shared_from_this, combined with the inherent thread safety of std::shared_ptr reference counting, enable us to ensure that the Client object cannot be destructed while the callback code is accessing its internal members.

Note that 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.

It's exactly the same in c++11 and later: It is to enable the ability to return this as a shared pointer since this gives you a raw pointer.

in other word, it allows you to turn code like this

class Node {
public:
    Node* getParent const() {
        if (m_parent) {
            return m_parent;
        } else {
            return this;
        }
    }

private:

    Node * m_parent = nullptr;
};           

into this:

class Node : std::enable_shared_from_this<Node> {
public:
    std::shared_ptr<Node> getParent const() {
        std::shared_ptr<Node> parent = m_parent.lock();
        if (parent) {
            return parent;
        } else {
            return shared_from_this();
        }
    }

private:

    std::weak_ptr<Node> m_parent;
};           
Related