2009-03-18 9 views
22

Tengo una base simple y una clase derivada que quiero que ambas tengan shared_from_this().Cómo enable_shared_from_this de ambos padres y derivado

Esta solución simple:

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

provoca excepción tr1::bad_weak_ptr en siguiente código:

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

Así que después de "googlear" He encontrado siguiente solución:

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

Y ahora trabajos.

¿Hay alguna manera mejor y más conveniente y correcta de enable_shared_from_this para padres y niños?

Gracias

Respuesta

10

Lo sentimos, pero no lo hay. El problema es que shared_ptr<foo> y shared_ptr<bar1> son tipos diferentes. No entiendo todo lo que ocurre bajo el capó, pero creo que que cuando el constructor regresa y se asigna a shared_ptr<foo>, el weak_ptr<bar1> interno ve que nada lo señala (porque solo un shared_ptr<bar1> incrementaría el contador) y se restablece a sí mismo. Cuando llama al bar1::shared_from_this en get_callback, obtiene la excepción porque el weak_ptr interno no apunta a nada.

Básicamente, enable_shared_from_this parece que solo funciona de forma transparente desde una sola clase en una jerarquía. Si prueba implementing it manually, el problema debería ser obvio.

+2

he intentado implementar de forma manual, y en realidad no es tan difícil. En 'My :: SmartPointer ', compruebe si T se deriva de 'My :: enableSmartFromThis'. Si es así, no asigne un contador de referencia en el montón, pero use los miembros de 'My :: enableSmartFromThis'. Ahora 'My :: GetSmartFromThis (this)' se vuelve trivial, puede convertir 'this' en' My :: enableSmartFromThis * 'y encontrar el recuento de referencias existente. Incluso puede verificar si está tratando de convertir de Base a Derivado cuando Derivado deriva de 'My :: enableSmartFromThis'. – MSalters

22

La solución OP se puede hacer más conveniente al definir lo siguiente en la clase base.

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

Eso es muy elegante. +1 – sehe

2

Una solución similar a @evoskuil que reduce repetitivo en las clases derivadas en caso de que desee implementar una función shared_from_this().

EDIT: cleaner solution. usando funciones fuera de la clase. Esto también brinda más opciones y proporciona un patrón para usar con clases cuya interfaz no puede modificarse, pero deriva de enable_shared_from_this. Este patrón es shared_from(that).

Nota: El uso de auto para los tipos de devolución aquí dependerá de la edad de su compilador.

Esto requiere algunas funciones shim externos que se podrían colocar en un encabezado de biblioteca:

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

ejemplo, en acción:

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

prueba Compilación:

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

solución antes de - esto coloca las funciones en la clase base que tenía algunos problemas - en particular la no uniformidad entre la aplicación requerida para las clases 'normales' y clases de plantilla.
Además, la implementación en la clase base debería repetirse para jerarquías de clases nuevas que no son tan SECAS. Además, la función de clase base sufría la posibilidad de un uso incorrecto al suministrar un puntero de clase base desde un objeto diferente. El esquema más nuevo anterior lo evita por completo y la verificación de tiempo de ejecución (...) va.

aplicación antigua:

#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; 
} 
+0

¿Por qué usó la palabra clave 'template' en' derived_x'? – Myon

+1

@Myon esto se debe a que shared_from es una función de miembro de plantilla en la clase base y se llama desde dentro de una clase de plantilla. Sin las palabras clave 'this' o 'template' aquí, no se compilará en compiladores compatibles. La versión anterior de MSVC probablemente compilaría esta multa. Sin él, el compilador interpretará el símbolo '<' como un operador 'menor que'. Consulte la sección 17.2.4 (página 312) en el borrador actual del documento estándar de C++: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/n4713.pdf, – Pete

+0

@Myon llegó en realidad con una mejor forma de hacerlo, que evita por completo la fea no uniformidad de la clase de plantilla y también tiene una serie de otros efectos secundarios positivos; consulte la respuesta actualizada. – Pete

Cuestiones relacionadas