Как включить enable_shared_from_this для родительского и производного - PullRequest
28 голосов
/ 18 марта 2009

У меня есть простой базовый и производный класс, который я хочу, чтобы оба имели shared_from_this().

Это простое решение:

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());
    }
};

Вызывает исключение tr1::bad_weak_ptr в следующем коде:

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

Итак, после "поиска в Google" я нашел следующее решение:

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());
    }
};

И теперь это работает.

Есть ли лучший, более удобный и правильный путь к enable_shared_from_this для родителей и детей?

Спасибо

Ответы [ 3 ]

37 голосов
/ 24 августа 2015

Решение OP можно сделать более удобным, определив следующее для базового класса.

protected:
    template <typename Derived>
    std::shared_ptr<Derived> shared_from_base()
    {
        return std::static_pointer_cast<Derived>(shared_from_this());
    }
11 голосов
/ 21 марта 2009

Извините, но это не так.

Проблема в том, что shared_ptr<foo> и shared_ptr<bar1> - это разные типы. Я не понимаю всего, что происходит под капотом, но я думаю , что когда конструктор возвращается и присваивается shared_ptr<foo>, внутренний weak_ptr<bar1> видит, что на него ничего не указывает ( только shared_ptr<bar1> увеличивает счетчик) и сбрасывает себя. Когда вы звоните bar1::shared_from_this в get_callback, вы получаете исключение, потому что внутренний weak_ptr ни на что не указывает.

По сути, enable_shared_from_this, похоже, работает прозрачно только из одного класса в иерархии. Если вы попытаетесь реализовать его вручную , проблема должна стать очевидной.

5 голосов
/ 13 декабря 2017

Аналогичное решение @evoskuil, которое сокращает шаблон в производных классах, если вы захотите реализовать функцию shared_from_this(), в результате чего в точке использования в классе будет приведен следующий код:

auto shared_from_this() {
    return shared_from(this);
}  

При этом используются функции «shim» вне класса. Делая это таким образом, он также обеспечивает чистый способ сделать это для классов, интерфейс которых не может быть изменен, а является производным от enable_shared_from_this - например,

auto shared_that = shared_from(that);

Примечание: использование auto для типов возврата здесь будет зависеть от возраста вашего компилятора.

Функции Shim, которые можно поместить в заголовок библиотеки:

template <typename Base>
inline std::shared_ptr<Base>
shared_from_base(std::enable_shared_from_this<Base>* base) 
{
    return base->shared_from_this();
}
template <typename Base>
inline std::shared_ptr<const Base>
shared_from_base(std::enable_shared_from_this<Base> const* base) 
{
    return base->shared_from_this();
}
template <typename That>
inline std::shared_ptr<That>
shared_from(That* that) 
{
    return std::static_pointer_cast<That>(shared_from_base(that));
}

Приведенный выше код основан на том факте, что тип, переданный в shared_from(...), наследуется от std::enable_shared_from_this<Base> в какой-то момент его происхождения.

Позвонив по номеру shared_from_base, вы выясните, какой это был тип. Поскольку мы знаем, что That наследуется от Base, можно сделать статический уклон.

Возможно, есть некоторые патологические угловые случаи с классами, имеющими операторы преобразования типов ... но это вряд ли произойдет в коде, не предназначенном для этого.

Пример:

struct base : public std::enable_shared_from_this<base> {};
struct derived : public base
{
    auto shared_from_this() {
        return shared_from(this);
    }
    // Can also provide a version for const:
    auto shared_from_this() const {
        return shared_from(this);
    }
    // Note that it is also possible to use shared_from(...) from
    // outside the class, e.g. 
    // auto sp = shared_from(that);
};
template <typename X>
struct derived_x : public derived
{
    auto shared_from_this() {
        return shared_from(this);
    }
};

Тест на компиляцию:

int main()
{
    auto pbase = std::make_shared<base>();
    auto pderived = std::make_shared<derived>();
    auto pderived_x = std::make_shared<derived_x<int> >();

    auto const& const_pderived = *pderived;
    const_pderived.shared_from_this();

    std::shared_ptr<base> test1 = pbase->shared_from_this();
    std::shared_ptr<derived> test2 = pderived->shared_from_this();
    std::shared_ptr<derived_x<int> > test3 = pderived_x->shared_from_this();

    return 0;
}

https://onlinegdb.com/SJWM5CYIG

Предыдущее решение , которое я опубликовал, сохранило, чтобы комментарии все еще имели смысл - это поместило функции в базовый класс, у которого были некоторые проблемы - особенно неравномерность между требуемой реализацией для «нормальных» классов и шаблоны классов.
Кроме того, реализация в базовом классе должна быть повторена для новых иерархий классов, что не является СУХОЙ. Кроме того, функция базового класса страдала от возможности неправильного использования, предоставляя указатель базового класса из другого объекта. Более новая схема, описанная выше, полностью исключает это, и проверка assert (...) во время выполнения выполняется.

Старая реализация:

#include <cassert>
#include <memory>

class base : public std::enable_shared_from_this<base>
{
protected:   
    template <typename T>
    std::shared_ptr<T> shared_from(T* derived) {
        assert(this == derived);
        return std::static_pointer_cast<T>(shared_from_this());
    }
};

class derived : public base
{
public:
    auto shared_from_this() {
        return shared_from(this);
    }
};

template <typename X>
class derived_x : public derived
{
public:
    auto shared_from_this() {
        return this->template shared_from(this);
    }
};

int main()
{
    auto pbase = std::make_shared<base>();
    auto pderived = std::make_shared<derived>();
    auto pderived_x = std::make_shared<derived_x<int> >();

    std::shared_ptr<base> test1 = pbase->shared_from_this();
    std::shared_ptr<derived> test2 = pderived->shared_from_this();
    std::shared_ptr<derived_x<int> > test3 = pderived_x->shared_from_this();

    return 0;
}
...