c++ - smart - unique pointer
¿Cuándo es std:: weak_ptr útil? (10)
Empecé a estudiar punteros inteligentes de C ++ 11 y no veo ningún uso útil de std::weak_ptr
. ¿Alguien puede decirme cuándo std::weak_ptr
es útil / necesario?
Al usar punteros, es importante comprender los diferentes tipos de punteros disponibles y cuándo tiene sentido usarlos. Hay cuatro tipos de punteros en dos categorías de la siguiente manera:
- Punteros crudos:
- Puntero sin
SomeClass* ptrToSomeClass = new SomeClass();
[es decir,SomeClass* ptrToSomeClass = new SomeClass();
]
- Puntero sin
- Punteros inteligentes:
- Punteros únicos [es decir,
std::unique_ptr<SomeClass> uniquePtrToSomeClass ( new SomeClass() );
] - Punteros compartidos [es decir,
std::shared_ptr<SomeClass> sharedPtrToSomeClass ( new SomeClass() );
] - Debilidad Punteros [es decir,
std::weak_ptr<SomeClass> weakPtrToSomeWeakOrSharedPtr ( weakOrSharedPtr );
]
- Punteros únicos [es decir,
Los punteros crudos (a los que a veces se hace referencia como "punteros heredados" o "punteros C") proporcionan un comportamiento de puntero "escueto" y son una fuente común de errores y pérdida de memoria. Los indicadores sin procesar no proporcionan ningún medio para hacer un seguimiento de la propiedad del recurso y los desarrolladores deben llamar ''eliminar'' manualmente para asegurarse de que no están creando una pérdida de memoria. Esto se vuelve difícil si el recurso se comparte, ya que puede ser un desafío saber si algún objeto todavía apunta al recurso. Por estas razones, generalmente se deben evitar los indicadores crudos y solo se deben usar en secciones de código crítico de rendimiento con alcance limitado.
Los punteros únicos son un puntero inteligente básico que ''posee'' el puntero sin procesar subyacente al recurso y es responsable de llamar a eliminar y liberar la memoria asignada una vez que el objeto que ''posee'' el puntero único queda fuera del alcance. El nombre ''único'' se refiere al hecho de que solo un objeto puede ''poseer'' el puntero único en un punto dado en el tiempo. La propiedad se puede transferir a otro objeto mediante el comando mover, pero un puntero único nunca se puede copiar ni compartir. Por estos motivos, los punteros únicos son una buena alternativa a los punteros sin procesar en el caso de que solo un objeto necesite el puntero en un momento determinado, lo que alivia la necesidad de liberar memoria al final del ciclo de vida del objeto propietario.
Los punteros compartidos son otro tipo de puntero inteligente que son similares a los punteros únicos, pero permiten que muchos objetos tengan propiedad sobre el puntero compartido. Al igual que el puntero único, los punteros compartidos son responsables de liberar la memoria asignada una vez que todos los objetos están listos apuntando al recurso. Lo logra con una técnica llamada recuento de referencias. Cada vez que un objeto nuevo toma posesión del puntero compartido, el recuento de referencias se incrementa en uno. De forma similar, cuando un objeto sale del alcance o deja de señalar el recurso, el recuento de referencia se reduce en uno. Cuando el recuento de referencias llega a cero, la memoria asignada se libera. Por estas razones, los punteros compartidos son un tipo muy poderoso de puntero inteligente que se debe usar cada vez que varios objetos necesitan apuntar al mismo recurso.
Finalmente, los punteros débiles son otro tipo de puntero inteligente que, en lugar de apuntar directamente a un recurso, apuntan a otro puntero (débil o compartido). Los punteros débiles no pueden acceder a un objeto directamente, pero pueden decir si el objeto aún existe o si ha expirado. Un puntero débil se puede convertir temporalmente en un puntero inteligente para acceder al objeto apuntado (siempre que exista). Para ilustrar, considere el siguiente ejemplo:
- Está ocupado y tiene reuniones superpuestas: Reunión A y Reunión B
- Usted decide ir a la Reunión A y su compañero de trabajo va a la Reunión B
- Le dice a su compañero de trabajo que si la Reunión B aún continúa después de que termine la Reunión A, se unirá
- Los siguientes dos escenarios podrían funcionar:
- La reunión A finaliza y la reunión B continúa, entonces te unes
- La reunión A termina y la reunión B también ha finalizado, por lo que no te unes
En el ejemplo, tiene un puntero débil a la Reunión B. No es un "propietario" en la Reunión B, por lo que puede finalizar sin usted, y usted no sabe si terminó o no, a menos que lo verifique. Si no ha terminado, puede unirse y participar, de lo contrario, no puede. Esto es diferente de tener un puntero compartido para la Reunión B porque entonces sería un "propietario" tanto en la Reunión A como en la Reunión B (participando en ambas al mismo tiempo).
El ejemplo ilustra cómo funciona un puntero débil y es útil cuando un objeto necesita ser un observador externo, pero no quiere la responsabilidad de la propiedad. Esto es particularmente útil en el caso de que dos objetos necesiten apuntarse el uno al otro (también conocido como referencia circular). Con punteros compartidos, ninguno de los objetos puede ser liberado porque el otro objeto todavía los señala con ''fuerza''. Con punteros débiles, se puede acceder a los objetos cuando sea necesario y se liberan cuando ya no necesitan existir.
Aquí hay un ejemplo, que me dio @jleahy: supongamos que tiene una colección de tareas, ejecutada de forma asíncrona y gestionada por un std::shared_ptr<Task>
. Es posible que desee hacer algo con esas tareas periódicamente, por lo que un evento de temporizador puede atravesar un std::vector<std::weak_ptr<Task>>
y dar a las tareas algo que hacer. Sin embargo, al mismo tiempo, una tarea puede haber decidido al mismo tiempo que ya no se necesita y muere. El temporizador puede verificar si la tarea todavía está activa haciendo un puntero compartido desde el puntero débil y usando ese puntero compartido, siempre que no sea nulo.
Existe un inconveniente del puntero compartido: shared_pointer no puede manejar la dependencia del ciclo hijo principal. Significa si la clase padre usa el objeto de la clase hija usando el puntero compartido, en el mismo archivo si la clase secundaria usa el objeto de la clase padre. el puntero compartido no podrá destruir todos los objetos, incluso el puntero compartido no llama al destructor en el escenario de dependencia de ciclo. básicamente, el puntero compartido no admite el mecanismo de recuento de referencias.
Este inconveniente lo podemos superar usando weak_pointer.
Otra respuesta, afortunadamente más simple. (para compañeros googlers)
Supongamos que tiene objetos de Team
y Member
.
Obviamente es una relación: el objeto Team
tendrá indicadores para sus Members
. Y es probable que los miembros también tengan un puntero a su objeto Team
.
Entonces tienes un ciclo de dependencia. Si usa shared_ptr
, los objetos ya no se liberarán automáticamente cuando abandone la referencia sobre ellos, porque se referencian entre sí de forma cíclica. Esta es una pérdida de memoria.
weak_ptr
esto usando weak_ptr
. El "propietario" generalmente usa shared_ptr
y "owned" usa un weak_ptr
para su parent y lo convierte temporalmente en shared_ptr
cuando necesita acceder a su padre.
Almacenar un ptr débil:
weak_ptr<Parent> parentWeakPtr_ = parentSharedPtr; // automatic conversion to weak from shared
luego úsala cuando sea necesario
shared_ptr<Parent> tempParentSharedPtr = parentWeakPtr_.lock(); // on the stack, from the weak ptr
if( not tempParentSharedPtr ) {
// yes it may failed if parent was freed since we stored weak_ptr
} else {
// do stuff
}
// tempParentSharedPtr is released when it goes out of scope
Son útiles con Boost.Asio cuando no se garantiza que un objeto de destino aún exista cuando se invoca un controlador asíncrono. El truco consiste en vincular un weak_ptr
en el objeto controlador asincrónico, utilizando las capturas std::bind
o lambda.
void MyClass::startTimer()
{
std::weak_ptr<MyClass> weak = shared_from_this();
timer_.async_wait( [weak, this](const boost::system::error_code& ec)
{
auto self = weak.lock();
if (self)
{
self->handleTimeout();
}
else
{
std::cout << "Target object no longer exists!/n";
}
} );
}
Esta es una variante del self = shared_from_this()
idioma que se ve a menudo en los ejemplos de Boost.Asio, donde un controlador asíncrono pendiente no prolongará la vida útil del objeto de destino, pero aún es seguro si se elimina el objeto de destino.
Un buen ejemplo sería un caché.
Para los objetos a los que se accedió recientemente, desea mantenerlos en la memoria, de modo que los mantenga en alto. Periódicamente, escanea el caché y decide a qué objetos no se ha accedido recientemente. No necesita mantener esos en la memoria, por lo que se deshace del puntero fuerte.
Pero, ¿y si ese objeto está en uso y algún otro código tiene un fuerte puntero? Si la memoria caché se deshace de su único puntero al objeto, nunca podrá volver a encontrarlo. Por lo tanto, la memoria caché mantiene un puntero débil a los objetos que necesita encontrar si permanecen en la memoria.
Esto es exactamente lo que hace un puntero débil: le permite ubicar un objeto si todavía está cerca, pero no lo mantiene si no lo necesita.
http://en.cppreference.com/w/cpp/memory/weak_ptr std :: weak_ptr es un puntero inteligente que contiene una referencia no propietaria ("débil") a un objeto administrado por std :: shared_ptr. Debe convertirse a std :: shared_ptr para acceder al objeto al que se hace referencia.
std :: weak_ptr modela la propiedad temporal: cuando un objeto necesita ser accedido solo si existe, y puede ser eliminado en cualquier momento por otra persona, std :: weak_ptr se usa para rastrear el objeto, y se convierte a std: : shared_ptr para asumir la propiedad temporal. Si el original std :: shared_ptr se destruye en este momento, la duración del objeto se extiende hasta que también se destruya el std :: shared_ptr temporal.
Además, std :: weak_ptr se usa para romper referencias circulares de std :: shared_ptr.
std::weak_ptr
es una muy buena forma de resolver el problema del puntero colgando . Simplemente usando punteros sin procesar es imposible saber si los datos a los que se hace referencia han sido desasignados o no. En cambio, al permitir que std::shared_ptr
administre los datos y suministre std::weak_ptr
a los usuarios de los datos, los usuarios pueden verificar la validez de los datos llamando a expired()
o lock()
.
No podría hacer esto con std::shared_ptr
solo, porque todas las instancias std::shared_ptr
comparten la propiedad de los datos que no se eliminan antes de que se eliminen todas las instancias de std::shared_ptr
. Aquí hay un ejemplo de cómo verificar el puntero colgando usando lock()
:
#include <iostream>
#include <memory>
int main()
{
// OLD, problem with dangling pointer
// PROBLEM: ref will point to undefined data!
int* ptr = new int(10);
int* ref = ptr;
delete ptr;
// NEW
// SOLUTION: check expired() or lock() to determine if pointer is valid
// empty definition
std::shared_ptr<int> sptr;
// takes ownership of pointer
sptr.reset(new int);
*sptr = 10;
// get pointer to data without taking ownership
std::weak_ptr<int> weak1 = sptr;
// deletes managed object, acquires new pointer
sptr.reset(new int);
*sptr = 5;
// get pointer to new data without taking ownership
std::weak_ptr<int> weak2 = sptr;
// weak1 is expired!
if(auto tmp = weak1.lock())
std::cout << *tmp << ''/n'';
else
std::cout << "weak1 is expired/n";
// weak2 points to new data (5)
if(auto tmp = weak2.lock())
std::cout << *tmp << ''/n'';
else
std::cout << "weak2 is expired/n";
}
weak_ptr
también es bueno para verificar la eliminación correcta de un objeto, especialmente en pruebas unitarias. El caso de uso típico podría verse así:
std::weak_ptr<X> weak_x{ shared_x };
shared_x.reset();
BOOST_CHECK(weak_x.lock());
... //do something that should remove all other copies of shared_x and hence destroy x
BOOST_CHECK(!weak_x.lock());
shared_ptr : contiene el objeto real.
weak_ptr : usa el lock
para conectarse al propietario real o devuelve NULL de lo contrario.
En términos generales, el rol weak_ptr
es similar al rol de la agencia de vivienda . Sin agentes, para conseguir una casa de alquiler, es posible que tengamos que verificar las casas al azar en la ciudad. Los agentes se aseguran de que solo visitemos aquellas casas que todavía están accesibles y disponibles para alquilar.