¿Cuál es la utilidad de `enable_shared_from_this`?

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?

Le permite obtener una instancia shared_ptr válida para 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 documentación de impulso para enable_shared_from_this :

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

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

 class Y: public enable_shared_from_this { public: shared_ptr f() { return shared_ptr(this); } } 

El puntero compartido que este devuelto tendrá un recuento de referencia diferente del "correcto", y uno de ellos terminará perdiendo y manteniendo una referencia colgante cuando se elimine el objeto.

enable_shared_from_this ha convertido en parte del estándar C ++ 11. También puede obtenerlo desde allí, así como de impulso.

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

… un código como este no funcionará correctamente:

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

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

De manera similar, si una función miembro necesita un objeto shared_ptr que sea el propietario del objeto al que se está llamando, no puede simplemente crear un objeto sobre la marcha:

 struct S { shared_ptr dangerous() { return shared_ptr(this); // don't do this! } }; int main() { shared_ptr sp1(new S); shared_ptr 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 miembro S::dangerous no tiene conocimiento de ese objeto shared_ptr , por lo que el objeto shared_ptr que devuelve es distinto de sp1 . Copiar el nuevo objeto shared_ptr a sp2 no ayuda; cuando sp2 sale del scope, liberará el recurso, y cuando sp1 queda fuera del scope, liberará el recurso nuevamente.

La forma de evitar este problema es usar 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; Me gusta esto:

 struct S : enable_shared_from_this { shared_ptr not_dangerous() { return shared_from_this(); } }; int main() { shared_ptr sp1(new S); shared_ptr sp2 = sp1->not_dangerous(); return 0; } 

Cuando haga esto, tenga en cuenta que el objeto al que llama shared_from_this debe ser propiedad de un objeto shared_ptr . Esto no funcionará:

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

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. Quizás otros comstackdores implementen enable_shared_from_this de manera diferente … *

enable_shared_from_this agrega una weak_ptr private weak_ptr a T que contiene el ‘ único recuento de referencia verdadero ‘ para la instancia de T

Entonces, cuando primero creas un shared_ptr en un T * nuevo, ese weak_ptr interno de ese T * se inicializa con un refcount de 1. El nuevo shared_ptr básicamente respalda este weak_ptr .

T puede entonces, en sus métodos, invocar shared_from_this para obtener una instancia de shared_ptr que retroceda al mismo recuento de referencia almacenado internamente . De esta forma, siempre tiene un lugar donde se almacena el ref-count de T* lugar de tener múltiples instancias de shared_ptr que no se conocen entre sí, y cada una cree que son las shared_ptr que se encargan de contar de nuevo T y eliminándolo cuando su conteo de ref llega a cero.

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.

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

en otras palabras, te permite convertir código como este

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

dentro de esto:

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

Otra forma es agregar un weak_ptr m_stub en la class Y A continuación, escribir:

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

Útil cuando no puede cambiar la clase de la que proviene (por ejemplo, ampliando la biblioteca de otras personas). No te olvides de inicializar el miembro, por ejemplo, mediante m_stub = shared_ptr(this) , es válido incluso durante un constructor.

Está bien si hay más fragmentos 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 la asignación finalice y se destruyan las variables temporales. Por lo tanto, mi respuesta es incorrecta.