Search code examples
c++inheritancec++14shared-ptrvirtual-destructor

Using enable_shared_from_this in polymorphic inheritance with virtual destructor


I have the following class structure for Managing callbacks with different prototypes:

class MethodHandlerBase: public std::enable_shared_from_this<MethodHandlerBase>{
public:
    virtual void operator()(void* data) = 0;
    virtual ~MethodHandlerBase(){}
};
   
class MethodHandlerA: public MethodHandlerBase{
private:
    MethodHandlerACallback cb;
public:
    MethodHandlerA(MethodHandlerACallback cb): cb(cb){}
    virtual void operator()(void* data);
};
    
class MethodHandlerB: public MethodHandlerBase{
private:
    MethodHandlerBCallback cb;
public:
    MethodHandlerB(MethodHandlerBCallback cb): cb(cb){}
    virtual void operator()(void* data);
};

In some cases MethodHandlerA or MethodHandlerB might use this (wrapped in a shared_ptr) in a lambda expression passed to elsewhere, so I need to be sure that it is correctly deleted when needed. Therefore I added the std::enable_shared_from_this<MethodHandlerBase> inheritance to the base class.

But I read that you usally cannot use std::enable_shared_from_this via inheritance (apart from using a template, which actually would not really be inheritance anymore). In my understanding this is due to the possible wrongly destruction of the instance. In this case I would assume my code would work properly since it uses a virtual destructor (which is needed anyway).

So am I right with my theory or is there something else going on about std::enable_shared_from_this inheritance that I did not understand?

EDIT:

To add a short examples of what I plan to use it like:

From inside the class:

void MethodHandlerB::operator()(void* data){
    std::shared_ptr<MethodHandlerB> thisPtr = std::dynamic_pointer_cast<MethodHandlerB>(this->shared_from_this());
    putLamdaToSomeGlobalEventThing([thisPtr](){
        thisPtr->doSomething();
    });
}

and from outside

std::vector<MethodHandlerBase> vec{std::make_shared<MethodHandlerB>()};

Solution

  • Some minor points:

    • You could move the shared pointer into the lambda to avoid an atomic increment and decrement
    • No need to use a dynamic pointer cast since you know for sure the dynamic type (plus you don't check the result is not empty anyway!)
    void MethodHandlerB::operator()(void* data){
        auto thisPtr = std::static_pointer_cast<MethodHandlerB>(this->shared_from_this());
        putLamdaToSomeGlobalEventThing([thisPtr = std::move(thisPtr)](){
            thisPtr->doSomething();
        });
    }
    
    • Alternatively, you could use separate captures for this and the shared pointer, which avoids the cast altogether:
    void MethodHandlerB::operator()(void* data){
        putLamdaToSomeGlobalEventThing([this, thisPtr = shared_from_this()](){
            doSomething();
        });
    }
    

    Edit: as one of the comments points out, if you don't use shared_from_this() directly on the base class, you're better off just deriving from enable_shared_from_this in the derived classes. You can do this because C++ supports multiple inheritence.

    class MethodHandlerBase {
    public:
        virtual void operator()(void* data) = 0;
        virtual ~MethodHandlerBase(){}
    };
       
    class MethodHandlerA: 
        public MethodHandlerBase,
        public std::enable_shared_from_this<MethodHandlerA>
    {
    private:
        MethodHandlerACallback cb;
    public:
        MethodHandlerA(MethodHandlerACallback cb): cb(cb){}
        virtual void operator()(void* data);
    };
    
    void MethodHandlerA::operator()(void* data){
        putLamdaToSomeGlobalEventThing([self = shared_from_this()](){
            self->doSomething();
        });
    }