c++ - smart - diferencias shared_ptr y weak_ptr
smart pointers c++ (5)
Los indicadores débiles simplemente "observan" el objeto gestionado; no lo "mantienen vivo" ni afectan su duración. A diferencia de shared_ptr
, cuando el último weak_ptr
sale del alcance o desaparece, el objeto apuntado puede seguir existiendo porque el weak_ptr
no afecta la vida útil del objeto, no tiene derechos de propiedad. El weak_ptr
se puede usar para determinar si el objeto existe y para proporcionar un shared_ptr
que se puede usar para referirse a él.
La definición de weak_ptr
está diseñada para que sea relativamente infalible, por lo que es muy poco lo que se puede hacer directamente con un weak_ptr
. Por ejemplo, no puedes desreferenciarlo; ni operator*
ni operator->
se define para un weak_ptr
. No puede acceder al puntero al objeto con él; no hay función get()
. Hay una función de comparación definida para que pueda almacenar weak_ptrs
en un contenedor ordenado, pero eso es todo.
Estoy leyendo el libro de Scott Meyers "Effective C ++". Se mencionó que hay tr1::shared_ptr
y tr1::weak_ptr
funcionan como punteros incorporados, pero hacen un seguimiento de cuántos tr1::shared_ptrs
apuntan a un objeto.
Esto se conoce como recuento de referencia. Esto funciona bien para evitar fugas de recursos en estructuras de datos acíclicas, pero si dos o más objetos contienen tr1::shared_ptrs
modo que se forme un ciclo, el ciclo puede mantener el recuento de referencias de cada uno por encima de cero, incluso cuando todos los punteros externos al ciclo tienen sido destruido
Ahí es donde tr1::weak_ptrs
.
Mi pregunta es cómo las estructuras cíclicas de datos hacen que la referencia cuente por encima de cero. Acepto amablemente un ejemplo de programa C ++. ¿Cómo se soluciona el problema con weak_ptrs
? (de nuevo, con el ejemplo, por favor).
Para futuros lectores.
Solo quiero señalar que la explicación dada por Atom es excelente, aquí está el código de trabajo
#include <memory> // and others
using namespace std;
class B; // forward declaration
// for clarity, add explicit destructor to see that they are not called
class A { public: shared_ptr<B> b; ~A() {cout << "~A()" << endl; } };
class B { public: shared_ptr<A> a; ~B() {cout << "~B()" << endl; } };
shared_ptr<A> x(new A); //x->b share_ptr is default initialized
x->b = make_shared<B>(); // you can''t do "= new B" on shared_ptr
x->b->a = x;
cout << x.use_count() << endl;
Permítame repetir su pregunta: "Mi pregunta, cómo las estructuras cíclicas de datos hacen que la referencia cuente por encima de cero, solicite amablemente que se muestre con el ejemplo en el programa C ++. Cómo se soluciona el problema mediante weak_ptrs
nuevamente, por ejemplo, por favor".
El problema ocurre con un código de C ++ como este (conceptualmente):
class A { shared_ptr<B> b; ... };
class B { shared_ptr<A> a; ... };
shared_ptr<A> x(new A); // +1
x->b = new B; // +1
x->b->a = x; // +1
// Ref count of ''x'' is 2.
// Ref count of ''x->b'' is 1.
// When ''x'' leaves the scope, there will be a memory leak:
// 2 is decremented to 1, and so both ref counts will be 1.
// (Memory is deallocated only when ref count drops to 0)
Para responder la segunda parte de su pregunta: matemáticamente es imposible que el recuento de referencias trate de ciclos. Por lo tanto, un weak_ptr
(que básicamente es una versión shared_ptr
de shared_ptr
) no se puede usar para resolver el problema del ciclo: el programador está resolviendo el problema del ciclo.
Para resolverlo, el programador debe conocer la relación de propiedad entre los objetos, o necesita inventar una relación de propiedad si no existe tal propiedad de forma natural.
El código C ++ anterior se puede cambiar para que A tenga B:
class A { shared_ptr<B> b; ... };
class B { weak_ptr<A> a; ... };
shared_ptr<A> x(new A); // +1
x->b = new B; // +1
x->b->a = x; // No +1 here
// Ref count of ''x'' is 1.
// Ref count of ''x->b'' is 1.
// When ''x'' leaves the scope, its ref count will drop to 0.
// While destroying it, ref count of ''x->b'' will drop to 0.
// So both A and B will be deallocated.
Una pregunta crucial es: ¿ weak_ptr
puede usar weak_ptr
en caso de que el programador no pueda distinguir la relación de propiedad y no pueda establecer ninguna propiedad estática debido a la falta de privilegios o la falta de información?
La respuesta es: si la propiedad entre los objetos no está clara, weak_ptr
no puede ayudar. Si hay un ciclo, el programador tiene que encontrarlo y romperlo. Un remedio alternativo es usar un lenguaje de programación con recolección completa de basura (como: Java, C #, Go, Haskell), o usar un recolector de basura conservador (= imperfecto) que funcione con C / C ++ (como: Boehm GC) .
Todas las respuestas anteriores son INCORRECTAS. weak_ptr
NO se usa para romper referencias cíclicas, tienen otro propósito.
Básicamente, si todas las shared_ptr(s)
fueron creadas por make_shared()
o allocate_shared()
llamadas, NUNCA necesitarás weak_ptr
si no tienes otro recurso que memoria para administrar. Estas funciones crean el objeto del contador de referencia shared_ptr
con el objeto en sí, y la memoria se liberará al mismo tiempo.
La única diferencia entre weak_ptr
y shared_ptr
es que el weak_ptr
permite que el objeto del contador de referencia se guarde después de liberar el objeto real. Como resultado, si mantiene una gran cantidad de shared_ptr
en un std::set
los objetos reales ocuparán mucha memoria si son lo suficientemente grandes. Este problema se puede resolver utilizando weak_ptr
en weak_ptr
lugar. En este caso, debe asegurarse de que weak_ptr
almacenado en el contenedor no haya expirado antes de usarlo.
Un shared_ptr
envuelve un mecanismo de conteo de referencia alrededor de un puntero sin formato. Por lo tanto, para cada instancia de shared_ptr
el recuento de referencias se incrementa en uno. Si dos objetos share_ptr
se refieren entre sí, nunca serán eliminados porque nunca terminarán con un recuento de referencias de cero.
weak_ptr
apunta a un shared_ptr
pero no aumenta su recuento de referencia. Esto significa que el objeto subyacente aún se puede eliminar aunque haya una referencia de weak_ptr
al mismo.
La forma en que esto funciona es que se puede usar el weak_ptr
para crear un shared_ptr
para cuando se quiera usar el objeto subyacente. Sin embargo, si el objeto ya ha sido eliminado, se devuelve una instancia vacía de un shared_ptr
. Dado que el recuento de referencias en el objeto subyacente no se incrementa con una referencia weak_ptr
, una referencia circular no dará como resultado que el objeto subyacente no se elimine.