Respuestas:
Le permite obtener una shared_ptr
instancia válida para this
, cuando todo lo que tiene es this
. Sin él, no tendría forma de obtener un shared_ptr
a this
, a menos que ya tuviera uno como miembro. Este ejemplo de la documentación de impulso para 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 una instancia miembro. Tenga en cuenta que no puede simplemente hacer esto:
class Y: public enable_shared_from_this<Y>
{
public:
shared_ptr<Y> f()
{
return shared_ptr<Y>(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
se ha convertido en parte del estándar C ++ 11. También puede obtenerlo desde allí, así como desde boost.
std::shared_ptr
constructor en un puntero sin formato si hereda de std::enable_shared_from_this
. No sé si la semántica de Boost se actualizó para respaldar esto.
std::shared_ptr
un objeto que ya está administrado por otro std::shared_ptr
no consultará la referencia débil almacenada internamente y, por lo tanto, conducirá a un comportamiento indefinido". ( en.cppreference.com/w/cpp/memory/enable_shared_from_this )
shared_ptr<Y> q = p
?
std::make_shared<T>
.
del artículo del Dr. Dobbs sobre punteros 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<int> sp1(ip);
shared_ptr<int> sp2(ip);
Ninguno de los dos shared_ptr
objetos conoce al otro, por lo que ambos intentarán liberar el recurso cuando sean destruidos. Eso generalmente lleva a problemas.
Del mismo modo, si una función miembro necesita un shared_ptr
objeto que posee el objeto al que se está llamando, no puede 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 shared_pt
objeto r sp1
posee el recurso recién asignado. El código dentro de la función miembro S::dangerous
no conoce ese shared_ptr
objeto, por lo que el shared_ptr
objeto que devuelve es distinto sp1
. Copiar el nuevo shared_ptr
objeto a sp2
no ayuda; cuando está sp2
fuera de alcance, liberará el recurso, y cuando sp1
salga del alcance, volverá a liberar el recurso.
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<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;
}
Cuando haga esto, tenga en cuenta que el objeto al que llama shared_from_this
debe ser propiedad de un shared_ptr
objeto. Esto no funcionará:
int main()
{
S *p = new S;
shared_ptr<S> sp2 = p->not_dangerous(); // don't do this
}
shared_ptr<S> sp1(new S);
usarlo shared_ptr<S> sp1 = make_shared<S>();
, puede preferir usar stackoverflow.com/questions/18301511/…
shared_ptr<S> sp2 = p->not_dangerous();
porque el problema aquí es que debes crear un shared_ptr de la manera normal antes de llamar shared_from_this()
la primera vez! ¡Esto es realmente fácil de equivocarse! Antes de C ++ 17, UB debe llamar shared_from_this()
antes de que se haya creado exactamente un shared_ptr de la manera normal: auto sptr = std::make_shared<S>();
o shared_ptr<S> sptr(new S());
. Afortunadamente, desde C ++ 17 en adelante, arrojará.
S* s = new S(); shared_ptr<S> ptr = s->not_dangerous();
<- Se permite llamar a shared_from_this solo en un objeto previamente compartido, es decir, en un objeto administrado por std :: shared_ptr <T>. De lo contrario, el comportamiento es indefinido (hasta C ++ 17) std :: bad_weak_ptr es arrojado (por el constructor shared_ptr desde un punto débil construido por defecto) (desde C ++ 17). . Entonces, la realidad es que debería llamarse always_dangerous()
, porque necesita saber si ya se ha compartido o no.
Aquí está mi explicación, desde una perspectiva de tuercas y pernos (la respuesta principal no 'hizo clic' conmigo). * Tenga en cuenta que este es el resultado de investigar el origen de shared_ptr y enable_shared_from_this que viene con Visual Studio 2012. Quizás otros compiladores implementen enable_shared_from_this de manera diferente ... *
enable_shared_from_this<T>
privada añade un weak_ptr<T>
ejemplo de T
lo que sostiene el ' verdadero contador de referencia ' para la instancia de T
.
Entonces, cuando creas un shared_ptr<T>
nuevo en un nuevo T *, el débil_ptr interno de ese T * se inicializa con un recuento de 1. El nuevo shared_ptr
básicamente retrocede en esto weak_ptr
.
T
puede, entonces, en sus métodos, llamar shared_from_this
para obtener una instancia de shared_ptr<T>
ese respaldo en el mismo recuento de referencia almacenado internamente . De esta manera, siempre tiene un lugar donde T*
se almacena el recuento de referencias en lugar de tener varias shared_ptr
instancias que no se conocen entre sí, y cada una piensa que son las shared_ptr
encargadas de contarlas T
y eliminarlas cuando su referencia -cuenta llega a cero.
So, when you first create...
porque es un requisito (ya que usted dice que el débil_ptr no se inicializa hasta que pasa el puntero de los objetos a un controlador_compartido) y este requisito es donde las cosas pueden salir terriblemente mal si está no es cuidadoso. Si no crea shared_ptr antes de llamar shared_from_this
, obtendrá UB; del mismo modo, si crea más de shared_ptr, también obtendrá UB. Tiene que asegurarse de alguna manera de crear un shared_ptr exactamente una vez.
enable_shared_from_this
es frágil para empezar, ya que el punto es poder obtener un shared_ptr<T>
de a T*
, pero en realidad cuando obtienes un puntero T* t
, generalmente no es seguro asumir nada acerca de que ya se ha compartido o no, y hacer una suposición equivocada es UB.
Tenga en cuenta que el uso de boost :: intrusive_ptr no sufre este problema. Esta suele ser una forma más conveniente de solucionar este problema.
enable_shared_from_this
permite trabajar con una API que acepta específicamente shared_ptr<>
. En mi opinión, dicha API suele estar haciendo mal (ya que es mejor dejar que algo más alto en la pila posea la memoria), pero si se ve obligado a trabajar con una API de este tipo, esta es una buena opción.
Es exactamente lo mismo en c ++ 11 y versiones posteriores: es para permitir la posibilidad de regresar this
como un puntero compartido, ya que this
le da un puntero sin formato.
en otras palabras, te permite activar 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<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;
};
shared_ptr
. Es posible que desee cambiar la interfaz para asegurarse de que sea así.
std::shared_ptr<Node> getParent const()
, normalmente lo expondría como en su NodePtr getParent const()
lugar. Si realmente necesita acceder al puntero sin formato interno (el mejor ejemplo: tratar con una biblioteca C), hay std::shared_ptr<T>::get
algo que odio mencionar porque he usado este descriptor de acceso sin formato demasiadas veces por la razón equivocada.
Otra forma es agregar un weak_ptr<Y> m_stub
miembro al class Y
. Luego escribir:
shared_ptr<Y> Y::f()
{
return m_stub.lock();
}
Útil cuando no puede cambiar la clase de la que deriva (por ejemplo, ampliar la biblioteca de otras personas). No olvide inicializar el miembro, por ejemplo m_stub = shared_ptr<Y>(this)
, por , es válido incluso durante un constructor.
Está bien si hay más trozos como este en la jerarquía de herencia, no evitará la destrucción del objeto.
Editar: como lo señaló correctamente el usuario nobar, el código destruiría el objeto Y cuando finalice la asignación y se destruyan las variables temporales. Por lo tanto mi respuesta es incorrecta.
shared_ptr<>
que no elimine a su puntero, esto es exagerado. Simplemente puede decir return shared_ptr<Y>(this, no_op_deleter);
dónde no_op_deleter
está un objeto de función unaria tomando Y*
y sin hacer nada.
m_stub = shared_ptr<Y>(this)
construirá e inmediatamente destruirá un shared_ptr temporal a partir de esto. Cuando termine esta declaración, this
se eliminará y todas las referencias posteriores estarán colgando.
enable_shared_from_this
, mantiene una parte weak_ptr
de sí misma (poblada por el ctor), devuelta como shared_ptr
cuando llama shared_from_this
. En otras palabras, está duplicando lo que enable_shared_from_this
ya proporciona.