How to enable_shared_from_this of both parent and derived

Viewed 22244

I have simple base and derived class that I want both have shared_from_this().

This simple solution:

class foo : public enable_shared_from_this<foo> {
    void foo_do_it()
    {
        cout<<"foo::do_it\n";
    }
public:
    virtual function<void()> get_callback()
    {
        return boost::bind(&foo::foo_do_it,shared_from_this());
    }
    virtual ~foo() {};
};

class bar1 : public foo , public enable_shared_from_this<bar1> {
    using enable_shared_from_this<bar1>::shared_from_this;
    void bar1_do_it()
    {
        cout<<"foo::do_it\n";
    }
public:
    virtual function<void()> get_callback()
    {
        return boost::bind(&bar1::bar1_do_it,shared_from_this());
    }
};

Causes exception tr1::bad_weak_ptr in following code:

shared_ptr<foo> ptr(shared_ptr<foo>(new bar1));
function<void()> f=ptr->get_callback();
f();

So after "googling" I have found following solution:

class bar2 : public foo {
    void bar2_do_it()
    {
        cout<<"foo::do_it\n";
    }
    shared_ptr<bar2> shared_from_this()
    {
        return boost::static_pointer_cast<bar2>(foo::shared_from_this());
    }
public:
    virtual function<void()> get_callback()
    {
        return boost::bind(&bar2::bar2_do_it,shared_from_this());
    }
};

And now it works.

Is there any better and more convinient and correct way to enable_shared_from_this for both parent and child?

Thanks

5 Answers

Sorry, but there isn't.

The problem is that shared_ptr<foo> and shared_ptr<bar1> are different types. I don't understand everything that's going on under the hood, but I think that when the constructor returns and is assigned to a shared_ptr<foo>, the internal weak_ptr<bar1> sees that nothing is pointing to it (because only a shared_ptr<bar1> would increment the counter) and resets itself. When you call bar1::shared_from_this in get_callback, you get the exception because the internal weak_ptr isn't pointing to anything.

Essentially, enable_shared_from_this only seems to work transparently from a single class in a hierarchy. If you try implementing it manually, the problem should become obvious.

Quite easy; inherit public shared_from_this only in your base class. Implement an accessor in your derived class that casts to the appropriate type;

std::shared_ptr<Derived> shared() 
{
   return std::dynamic_pointer_cast<Derived>(Base::shared_from_this());
}

With c++23 deducing this, things become much easier. https://godbolt.org/z/j499WK58Y

#include <memory>
#include <iostream>
#include <functional>
using namespace std;

struct new_enable_shared_from_this :
    public std::enable_shared_from_this<new_enable_shared_from_this> {
    
    template <typename Self>
    auto new_shared_from_this(this Self& self) {
        return std::static_pointer_cast<Self>(self.shared_from_this());
    }
};

class foo : public new_enable_shared_from_this {
    void foo_do_it()
    {
        cout<<"foo::do_it\n";
    }
public:
    virtual function<void()> get_callback()
    {
        return bind(&foo::foo_do_it,new_shared_from_this());
    }
    virtual ~foo() {};
};

class bar1 : public foo {
    void bar1_do_it()
    {
        cout<<"foo::do_it\n";
    }
public:
    virtual function<void()> get_callback()
    {
        return bind(&bar1::bar1_do_it,new_shared_from_this());
    }
};

int main() {
    auto pf = std::make_shared<foo>();
    pf->get_callback()();
    auto pb = std::make_shared<bar1>();
    pb->get_callback()();
}

This is the previous answer:

Well, I don't like virtual function. Virtual function is just for type erasing, but we don't need type erasing all the time. So, provide a mechanism for type erasing is enough. Here is an example that don't use virtual function:

#include <iostream>
#include <functional>
#include <memory>
using namespace std;

template<typename derived>
class foo_imp {
    void foo_do_it()
    {
        cout<<"foo::do_it\n";
    }
public:
    function<void()> get_callback()
    {
        auto&& d = static_cast<derived&>(*this);
        return bind(&foo_imp::foo_do_it, d.shared_from_this());
    }
};

template<typename derived>
class bar_imp {
    void bar_do_it()
    {
        cout<<"bar::do_it\n";
    }
public:
    function<void()> get_callback()
    {
        auto&& d = static_cast<derived&>(*this);
        return bind(&bar_imp::bar_do_it, d.shared_from_this());
    }
};

struct foo : public foo_imp<foo>, public enable_shared_from_this<foo> {};
struct bar : public bar_imp<bar>, public enable_shared_from_this<bar> {};

struct v_foo {
    virtual function<void()> get_callback() = 0;
};

template <typename T>
std::shared_ptr<v_foo> convert(const std::shared_ptr<T>& st) {
    struct _ : public v_foo {
        _(const std::shared_ptr<T>& st) : _st{st} {}
        function<void()> get_callback() override {
            return _st->get_callback();
        }
        std::shared_ptr<T> _st;
    };
    return std::make_shared<_>(st);
}

int main() {
    auto sf = make_shared<bar>();
    sf->get_callback()();

    auto svf = convert(sf);
    svf->get_callback()();

    auto sb = make_shared<foo>();
    sb->get_callback()();

    auto svb = convert(sb);
    svb->get_callback()();
}
Related