2009-04-03 10 views
270

Me encontré con enable_shared_from_this mientras leía los ejemplos de Boost.Asio y después de leer la documentación todavía estoy perdido por cómo debería usarse correctamente. ¿Puede alguien darme un ejemplo y/o una explicación de cuando usar esta clase tiene sentido?¿Cuál es la utilidad de `enable_shared_from_this`?

Respuesta

285

Le permite obtener una instancia shared_ptr válida a this, cuando todo lo que tiene es this. Sin él, no habría manera de obtener un shared_ptr a this, a menos que ya tenga uno como miembro. Este ejemplo de la boost documentation for enable_shared_from_this:

class Y: public enable_shared_from_this<Y> 
{ 
public: 

    shared_ptr<Y> f() 
    { 
     return shared_from_this(); 
    } 
} 

int main() 
{ 
    shared_ptr<Y> p(new Y); 
    shared_ptr<Y> q = p->f(); 
    assert(p == q); 
    assert(!(p < q || q < p)); // p and q must share ownership 
} 

El método f() devuelve un válido shared_ptr, a pesar de que no tenía ejemplo miembro. Tenga en cuenta que no se puede simplemente hacer esto:

class Y: public enable_shared_from_this<Y> 
{ 
public: 

    shared_ptr<Y> f() 
    { 
     return shared_ptr<Y>(this); 
    } 
} 

El puntero compartida que esta vuelto tendrá un contador de referencia diferente de la "correcta" uno, y uno de ellos va a terminar perdiendo y la celebración de una referencia colgando cuando el objeto esta borrado.

enable_shared_from_this va a formar parte del nuevo estándar C++ 0x también, por lo que también puede obtenerlo desde allí o desde el impulso.

+155

+1. El punto clave es que la técnica "obvia" de simplemente devolver shared_ptr (this) está rota, porque esto termina creando múltiples objetos shared_ptr distintos con recuentos de referencia separados. Por este motivo, nunca debe crear más de un shared_ptr ** del mismo puntero sin formato **. –

+0

Se debe tener en cuenta que en _C++ 11 y posterior_, es ** perfectamente válido ** usar un constructor 'std :: shared_ptr' en un _raw pointer_ ** si ** hereda de' std :: enable_shared_from_this' . ** No sé si ** la semántica de Boost se actualizó para apoyar esto. –

-2

Otra forma es agregar un miembro weak_ptr<Y> m_stub en class Y. A continuación, escriba:

shared_ptr<Y> Y::f() 
{ 
    return m_stub.lock(); 
} 

útil cuando no se puede cambiar la clase va a derivar a partir de (por ejemplo, extender la biblioteca de otras personas). No olvide inicializar al miembro, p. por m_stub = shared_ptr<Y>(this), es válido incluso durante un constructor.

Está bien si hay más trozos como este en la jerarquía de herencia, no impedirá la destrucción del objeto.

Editar: Como señala correctamente el usuario nobar, el código destruiría el objeto Y cuando se termine la asignación y se destruyan las variables temporales. Por lo tanto, mi respuesta es incorrecta.

+3

Si su intención aquí es producir un 'shared_ptr <>' que no elimine su punta, esto es overkill.Simplemente puede decir 'return shared_ptr (this, no_op_deleter);' donde 'no_op_deleter' es un objeto de función unario que toma' Y * 'y no hace nada. –

+1

Parece poco probable que esta sea una solución funcional. 'm_stub = shared_ptr (this)' construirá y destruirá inmediatamente un shared_ptr temporal a partir de esto. Cuando termine esta declaración, 'this' se eliminará y todas las referencias subsiguientes estarán colgando. – nobar

+1

El autor reconoce que esta respuesta es incorrecta, por lo que probablemente podría simplemente eliminarla. Pero la última vez que se conectó fue de 4.5 años, por lo que no es probable que lo haga. ¿Podría alguien con poderes superiores eliminar esta red arenque? –

153

del artículo Dr. Dobbs sobre los punteros débiles, creo que este ejemplo es más fácil de entender (fuente: http://drdobbs.com/cpp/184402026):

... código como este no funcionará correctamente:

int *ip = new int; 
shared_ptr<int> sp1(ip); 
shared_ptr<int> sp2(ip); 

Ni de los dos objetos shared_ptr sabe sobre el otro, por lo que ambos intentarán liberar el recurso cuando se destruyan. Eso usualmente lleva a problemas.

Del mismo modo, si una función miembro necesita un objeto shared_ptr que posee el objeto que está siendo llamado en adelante, puede no sólo crear un objeto sobre la marcha:

struct S 
{ 
    shared_ptr<S> dangerous() 
    { 
    return shared_ptr<S>(this); // don't do this! 
    } 
}; 

int main() 
{ 
    shared_ptr<S> sp1(new S); 
    shared_ptr<S> sp2 = sp1->dangerous(); 
    return 0; 
} 

Este código tiene el mismo problema que el ejemplo anterior, aunque en una forma más sutil. Cuando se construye, el objeto shared_pt r sp1 posee el recurso recientemente asignado. El código dentro de la función de miembro S::dangerous no conoce el objeto shared_ptr, por lo que el objeto shared_ptr que devuelve es distinto de sp1.Copiar el nuevo objeto shared_ptr al sp2 no ayuda; cuando sp2 sale del alcance, liberará el recurso, y cuando sp1 quede fuera del alcance, liberará el recurso nuevamente.

La forma de evitar este problema es utilizar la plantilla de clase enable_shared_from_this. La plantilla toma un argumento de tipo de plantilla, que es el nombre de la clase que define el recurso administrado. Esa clase debe, a su vez, derivarse públicamente de la plantilla; de esta manera:

struct S : enable_shared_from_this<S> 
{ 
    shared_ptr<S> not_dangerous() 
    { 
    return shared_from_this(); 
    } 
}; 

int main() 
{ 
    shared_ptr<S> sp1(new S); 
    shared_ptr<S> sp2 = sp1->not_dangerous(); 
    return 0; 
} 

Al hacer esto, tenga en cuenta que el objeto sobre el que se llama a shared_from_this debe ser propiedad de un objeto shared_ptr. Esto no funcionará:

int main() 
{ 
    S *p = new S; 
    shared_ptr<S> sp2 = p->not_dangerous();  // don't do this 
} 
+7

Gracias, esto ilustra que el problema se resuelve mejor que la respuesta actualmente aceptada. – goertzenator

+1

+1: Buena respuesta. Como un aparte, en lugar de 'shared_ptr sp1 (new S);' se puede preferir usar 'shared_ptr sp1 = make_shared ();', ver por ejemplo http://stackoverflow.com/questions/18301511/stdshared -ptr-initialization-make-sharedfoo-vs-shared-ptrtnew-foo – Arun

+3

Estoy bastante seguro de que la última línea debería leer 'shared_ptr sp2 = p-> not_dangerous();' porque la trampa aquí es que debes ** crea un shared_ptr de la manera normal antes de llamar a 'shared_from_this()' la primera vez! ** ¡Es muy fácil equivocarse! Antes de C++ 17 es ** UB ** llamar a 'shared_from_this()' antes de que se haya creado exactamente un shared_ptr de la manera normal: 'auto sptr = std :: make_shared ();' o 'shared_ptr sptr (new S()); '. Afortunadamente desde C++ 17 en adelante lo hará tirar. – AnorZaken

3

Tenga en cuenta que el uso de boost :: intrusive_ptr no presenta este problema. Esta es a menudo una forma más conveniente de evitar este problema.

+0

Sí, pero 'enable_shared_from_this' le permite trabajar con una API que acepta específicamente' shared_ptr <> '. En mi opinión, tal API suele ser * Doing It Wrong * (ya que es mejor dejar que algo más alto en la pila sea de la memoria) pero si se ve obligado a trabajar con una API así, esta es una buena opción. – cdunn2001

23

Aquí está mi explicación, desde una perspectiva de tuercas y tornillos (la respuesta superior no hizo 'clic' conmigo). * Tenga en cuenta que este es el resultado de la investigación de la fuente de shared_ptr y enable_shared_from_this que viene con Visual Studio 2012. Tal vez otros compiladores implementan enable_shared_from_this diferente ... *

enable_shared_from_this<T> añade un weak_ptr<T> instancia privada a T que mantiene el 'uno verdadero recuento de referencia 'para la instancia de T.

Por lo tanto, cuando se crea un shared_ptr<T> en una nueva T *, que weak_ptr interna T * 's se inicializa con un refcount de 1. El nuevo shared_ptr respalda básicamente en este weak_ptr.

T puede entonces, en sus métodos, llamar shared_from_this para obtener una instancia de shared_ptr<T> que mete hacia el mismo número de referencia almacenada internamente. De esta manera, siempre tiene un lugar donde se almacena el ref-count de T* en lugar de tener múltiples instancias de shared_ptr que no se conocen entre sí, y cada una cree que son shared_ptr que se encargan del conteo de ref T y borran cuando su recuento de ref llega a cero.

+1

Esto es correcto, y la parte realmente importante es 'Entonces, cuando crea ...' porque es un ** requisito ** (como usted dice, el weak_ptr no se inicializa hasta que pase el puntero de los objetos a un shared_ptr ctor!) y este requisito es donde las cosas pueden salir terriblemente mal si no tienes cuidado. Si no creas ningún shared_ptr antes de llamar a 'shared_from_this' obtienes UB, del mismo modo si creas más de un shared_ptr también obtienes UB. De alguna manera, debe asegurarse de crear una shared_ptr _exactly_ una vez. – AnorZaken

+1

En otras palabras, la idea de 'enable_shared_from_this' es frágil, ya que el punto es ser capaz de obtener' shared_ptr 'de' T * ', pero en realidad cuando obtienes un puntero' T * t' generalmente no es seguro asumir que ya se comparte o no, y hacer la suposición incorrecta es UB. – AnorZaken

+0

"_internal weak_ptr se inicializa con un refcount de 1_" weak ptr to T no posee ptr inteligente a T. Un ptr débil es un ref inteligente poseedor de suficiente información para hacer un ptr propietario que es una "copia" de otro ptr propietario . Un ptr débil no tiene recuento de ref. Tiene acceso a un recuento de ref, como todos los propietarios de ref. – curiousguy

2

Es exactamente lo mismo en C++ 11 y posterior: es para habilitar la capacidad de devolver this como un puntero compartido ya que this le da un puntero sin formato.

en otras palabras, que le permita activar un código como éste

class Node { 
public: 
    Node* getParent const() { 
     if (m_parent) { 
      return m_parent; 
     } else { 
      return this; 
     } 
    } 

private: 

    Node * m_parent = nullptr; 
};   

en esto:

class Node : std::enable_shared_from_this<Node> { 
public: 
    std::shared_ptr<Node> getParent const() { 
     std::shared_ptr<Node> parent = m_parent.lock(); 
     if (parent) { 
      return parent; 
     } else { 
      return shared_from_this(); 
     } 
    } 

private: 

    std::weak_ptr<Node> m_parent; 
};   
+0

Esto solo funcionará si estos objetos siempre son administrados por un 'shared_ptr'. Es posible que desee cambiar la interfaz para asegurarse de que sea el caso. – curiousguy

+0

Tiene toda la razón @curiousguy. Esto es evidente.También me gusta escribir todo mi shared_ptr para mejorar la legibilidad al definir mis API públicas. Por ejemplo, en lugar de 'std :: shared_ptr getParent const()', normalmente lo expondría como 'NodePtr getParent const()' en su lugar. Si necesitas absolutamente acceso al puntero interno sin procesar (mejor ejemplo: tratar con una biblioteca C), hay 'std :: shared_ptr :: get' para eso, lo cual detesto mencionar porque tengo este acceso puntero sin formato usado demasiados veces por la razón equivocada. – mchiasson

Cuestiones relacionadas