Подтвердить что ты не робот

Как включить_shared_from_ этого как из родительского, так и из производного

У меня есть простой базовый и производный класс, который я хочу иметь как 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();

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

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 для родителя и ребенка?

Спасибо

4b9b3361

Ответ 1

Извините, но его нет.

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

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

Ответ 2

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

protected:
    template <typename Derived>
    std::shared_ptr<Derived> shared_from_base()
    {
        return std::static_pointer_cast<Derived>(shared_from_this());
    }

Ответ 3

Аналогичное решение @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;
}