Respuestas:
Un buen ejemplo sería un caché.
Para los objetos accedidos recientemente, desea mantenerlos en la memoria, de modo que mantenga un puntero fuerte sobre ellos. Periódicamente, escanea el caché y decide a qué objetos no se ha accedido recientemente. No necesita mantenerlos en la memoria, por lo que se deshace del fuerte puntero.
Pero, ¿qué pasa si ese objeto está en uso y algún otro código tiene un puntero fuerte? Si el caché se deshace de su único puntero al objeto, nunca podrá volver a encontrarlo. Por lo tanto, el 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 localizar un objeto si todavía está cerca, pero no lo mantiene si nada más lo necesita.
std::weak_ptr
es una muy buena manera de resolver el problema del puntero colgante . Simplemente usando punteros sin procesar es imposible saber si los datos referenciados se han desasignado o no. En cambio, al permitir std::shared_ptr
administrar los datos y suministrarlos std::weak_ptr
a los usuarios de los datos, los usuarios pueden verificar la validez de los datos llamando expired()
o lock()
.
No podría hacer esto std::shared_ptr
solo, porque todas las std::shared_ptr
instancias comparten la propiedad de los datos que no se eliminan antes de que se eliminen todas las instancias std::shared_ptr
. Aquí hay un ejemplo de cómo verificar el puntero colgante 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";
}
std::weak_ptr::lock
crea una nueva std::shared_ptr
que comparte la propiedad del objeto administrado.
Otra respuesta, con suerte más simple. (para compañeros de googlers)
Supongamos que tienes Team
y Member
objetos.
Obviamente es una relación: el Team
objeto tendrá punteros a su Members
. Y es probable que los miembros también tengan un puntero hacia atrás a su Team
objeto.
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 manera cíclica. Esta es una pérdida de memoria.
Rompes esto usando weak_ptr
. El "dueño" suelen utilizar shared_ptr
y la "propiedad" utilizar una weak_ptr
a su padre, y lo convierten temporalmente a shared_ptr
cuando se necesita acceder a su matriz.
Almacene un ptr débil:
weak_ptr<Parent> parentWeakPtr_ = parentSharedPtr; // automatic conversion to weak from shared
luego úselo cuando sea necesario
shared_ptr<Parent> tempParentSharedPtr = parentWeakPtr_.lock(); // on the stack, from the weak ptr
if( !tempParentSharedPtr ) {
// yes, it may fail if the parent was freed since we stored weak_ptr
} else {
// do stuff
}
// tempParentSharedPtr is released when it goes out of scope
shared_ptr
es compartir la propiedad, por lo que nadie tiene la responsabilidad particular de liberar la memoria, se libera automáticamente cuando ya no se usa. A menos que haya un bucle ... Puede tener varios equipos compartiendo el mismo jugador (¿equipos anteriores?). Si el objeto del equipo "posee" a los miembros, no hay necesidad de utilizar un shared_ptr
para empezar.
shared_ptr
referenciado por sus "miembros del equipo", ¿cuándo será destruido? Lo que está describiendo es un caso en el que no hay bucle.
Aquí hay un ejemplo que me dio @jleahy: supongamos que tiene una colección de tareas, ejecutadas de forma asincrónica y administradas 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 ay std::vector<std::weak_ptr<Task>>
dar a las tareas algo que hacer. Sin embargo, simultáneamente, una tarea puede haber decidido simultáneamente que ya no es necesaria y morir. Por lo tanto, el temporizador puede verificar si la tarea aún está viva haciendo un puntero compartido desde el puntero débil y utilizando ese puntero compartido, siempre que no sea nulo.
Son útiles con Boost.Asio cuando no se garantiza que todavía exista un objeto de destino cuando se invoca un controlador asincrónico. El truco consiste en unir un weak_ptr
objeto de controlador asíncrono, utilizando std::bind
o capturas lambda.
void MyClass::startTimer()
{
std::weak_ptr<MyClass> weak = shared_from_this();
timer_.async_wait( [weak](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 asincrónico pendiente no prolongará la vida útil del objeto de destino, pero aún es seguro si se elimina el objeto de destino.
this
self = shared_from_this()
idioma cuando el controlador invoca métodos dentro de la misma clase.
shared_ptr : contiene el objeto real.
weak_ptr : se usa lock
para conectarse con el propietario real o, de lo shared_ptr
contrario, devuelve un NULL .
En términos generales, el weak_ptr
papel es similar al papel de la agencia de vivienda . Sin agentes, para obtener una casa en alquiler, es posible que tengamos que verificar casas al azar en la ciudad. Los agentes se aseguran de que visitemos solo aquellas casas que todavía son accesibles y están disponibles para alquilar.
weak_ptr
También es bueno 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());
Cuando se usan punteros, es importante comprender los diferentes tipos de punteros disponibles y cuándo tiene sentido usar cada uno. Hay cuatro tipos de punteros en dos categorías de la siguiente manera:
SomeClass* ptrToSomeClass = new SomeClass();
]std::unique_ptr<SomeClass> uniquePtrToSomeClass ( new SomeClass() );
std::shared_ptr<SomeClass> sharedPtrToSomeClass ( new SomeClass() );
std::weak_ptr<SomeClass> weakPtrToSomeWeakOrSharedPtr ( weakOrSharedPtr );
Los punteros sin formato (a veces denominados "punteros heredados" o "punteros en C") proporcionan un comportamiento de puntero "básico" y son una fuente común de errores y pérdidas de memoria. Los punteros sin formato no proporcionan medios para realizar un seguimiento de la propiedad del recurso y los desarrolladores deben llamar a '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, los punteros sin formato generalmente deben evitarse y solo usarse en secciones críticas del rendimiento del código con un 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 se sale del alcance. El nombre 'único' se refiere al hecho de que solo un objeto puede 'poseer' el puntero único en un momento dado. La propiedad se puede transferir a otro objeto mediante el comando de movimiento, pero un puntero único nunca se puede copiar o compartir. Por estas razones, los punteros únicos son una buena alternativa a los punteros sin formato en el caso de que solo un objeto necesite el puntero en un momento dado, y esto alivia al desarrollador de 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 terminan apuntando al recurso. Lo logra con una técnica llamada recuento de referencias. Cada vez que un nuevo objeto toma posesión del puntero compartido, el recuento de referencia se incrementa en uno. De manera similar, cuando un objeto sale del alcance o deja de apuntar al recurso, el recuento de referencia se reduce en uno. Cuando el recuento de referencia 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 siempre que varios objetos necesiten 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 todavía existe o si ha expirado. Un puntero débil se puede convertir temporalmente en un puntero compartido para acceder al objeto señalado (siempre que todavía exista). Para ilustrar, considere el siguiente ejemplo:
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 terminar sin usted y no sabe si terminó o no, a menos que marque. Si no ha terminado, puedes unirte y participar, de lo contrario, no puedes. Esto es diferente a tener un puntero compartido a la Reunión B porque sería un "propietario" en la Reunión A y 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 compartir la propiedad. Esto es particularmente útil en el escenario en el que dos objetos deben señalarse entre sí (también conocido como referencia circular). Con los punteros compartidos, ninguno de los objetos puede liberarse porque el otro objeto todavía los apunta 'fuertemente'. Cuando uno de los punteros es un puntero débil, el objeto que lo sostiene aún puede acceder al otro objeto cuando sea necesario, siempre que todavía exista.
Además de los otros casos de uso válidos ya mencionados, std::weak_ptr
es una herramienta increíble en un entorno multiproceso, porque
std::shared_ptr
en conjunto con std::weak_ptr
es seguro contra punteros colgantes - en oposición a std::unique_ptr
en conjunto con punteros sin procesarstd::weak_ptr::lock()
es una operación atómica (consulte también Acerca de la seguridad de subprocesos de weak_ptr )Considere una tarea para cargar todas las imágenes de un directorio (~ 10.000) simultáneamente en la memoria (por ejemplo, como un caché de miniaturas). Obviamente, la mejor manera de hacer esto es un subproceso de control, que maneja y administra las imágenes, y múltiples subprocesos de trabajo, que cargan las imágenes. Ahora esta es una tarea fácil. Aquí hay una implementación muy simplificada ( join()
se omite, etc., los hilos tendrían que manejarse de manera diferente en una implementación real, etc.)
// a simplified class to hold the thumbnail and data
struct ImageData {
std::string path;
std::unique_ptr<YourFavoriteImageLibData> image;
};
// a simplified reader fn
void read( std::vector<std::shared_ptr<ImageData>> imagesToLoad ) {
for( auto& imageData : imagesToLoad )
imageData->image = YourFavoriteImageLib::load( imageData->path );
}
// a simplified manager
class Manager {
std::vector<std::shared_ptr<ImageData>> m_imageDatas;
std::vector<std::unique_ptr<std::thread>> m_threads;
public:
void load( const std::string& folderPath ) {
std::vector<std::string> imagePaths = readFolder( folderPath );
m_imageDatas = createImageDatas( imagePaths );
const unsigned numThreads = std::thread::hardware_concurrency();
std::vector<std::vector<std::shared_ptr<ImageData>>> splitDatas =
splitImageDatas( m_imageDatas, numThreads );
for( auto& dataRangeToLoad : splitDatas )
m_threads.push_back( std::make_unique<std::thread>(read, dataRangeToLoad) );
}
};
Pero se vuelve mucho más complicado si desea interrumpir la carga de las imágenes, por ejemplo, porque el usuario ha elegido un directorio diferente. O incluso si quieres destruir al gerente.
Necesitaría comunicación de subprocesos y tendría que detener todos los subprocesos del cargador, antes de que pueda cambiar su m_imageDatas
campo. De lo contrario, los cargadores continuarían cargando hasta que todas las imágenes estén listas, incluso si ya están obsoletas. En el ejemplo simplificado, eso no sería demasiado difícil, pero en un entorno real las cosas pueden ser mucho más complicadas.
Los subprocesos probablemente serían parte de un grupo de subprocesos utilizado por varios administradores, de los cuales algunos se están deteniendo y otros no, etc. El parámetro simple imagesToLoad
sería una cola bloqueada, en la que esos gerentes envían sus solicitudes de imagen desde diferentes subprocesos de control con los lectores haciendo estallar las solicitudes, en un orden arbitrario, en el otro extremo. Y así la comunicación se vuelve difícil, lenta y propensa a errores. Una forma muy elegante de evitar cualquier comunicación adicional en tales casos es usar std::shared_ptr
en conjunto con std::weak_ptr
.
// a simplified reader fn
void read( std::vector<std::weak_ptr<ImageData>> imagesToLoad ) {
for( auto& imageDataWeak : imagesToLoad ) {
std::shared_ptr<ImageData> imageData = imageDataWeak.lock();
if( !imageData )
continue;
imageData->image = YourFavoriteImageLib::load( imageData->path );
}
}
// a simplified manager
class Manager {
std::vector<std::shared_ptr<ImageData>> m_imageDatas;
std::vector<std::unique_ptr<std::thread>> m_threads;
public:
void load( const std::string& folderPath ) {
std::vector<std::string> imagePaths = readFolder( folderPath );
m_imageDatas = createImageDatas( imagePaths );
const unsigned numThreads = std::thread::hardware_concurrency();
std::vector<std::vector<std::weak_ptr<ImageData>>> splitDatas =
splitImageDatasToWeak( m_imageDatas, numThreads );
for( auto& dataRangeToLoad : splitDatas )
m_threads.push_back( std::make_unique<std::thread>(read, dataRangeToLoad) );
}
};
Esta implementación es casi tan fácil como la primera, no necesita ninguna comunicación adicional de subprocesos y podría ser parte de un grupo / cola de subprocesos en una implementación real. Dado que las imágenes caducadas se omiten y las imágenes no caducadas se procesan, los hilos nunca tendrían que detenerse durante el funcionamiento normal. Siempre puede cambiar la ruta de forma segura o destruir a sus gerentes, ya que el lector fn comprueba si el puntero propietario no ha caducado.
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 referenciado.
std :: weak_ptr modela la propiedad temporal: cuando se necesita acceder a un objeto solo si existe, y alguien más puede eliminarlo en cualquier momento, std :: weak_ptr se usa para rastrear el objeto y se convierte a std: : shared_ptr para asumir la propiedad temporal. Si el std :: shared_ptr original se destruye en este momento, la vida útil del objeto se extiende hasta que el std :: shared_ptr temporal también se destruya.
Además, std :: weak_ptr se usa para romper las referencias circulares de std :: shared_ptr.
Hay un inconveniente del puntero compartido: shared_pointer no puede manejar la dependencia del ciclo padre-hijo. Significa si la clase primaria usa el objeto de la clase secundaria usando un puntero compartido, en el mismo archivo si la clase secundaria usa el objeto de la clase primaria. El puntero compartido no podrá destruir todos los objetos, incluso el puntero compartido no está llamando al destructor en el escenario de dependencia del ciclo. Básicamente, el puntero compartido no admite el mecanismo de recuento de referencias.
Este inconveniente podemos superarlo usando weak_pointer.
weak_ptr
acuerdo con una dependencia circular sin cambios en la lógica del programa como un reemplazo directo parashared_ptr
?" :-)
Cuando no queremos ser dueños del objeto:
Ex:
class A
{
shared_ptr<int> sPtr1;
weak_ptr<int> wPtr1;
}
En la clase anterior, wPtr1 no posee el recurso señalado por wPtr1. Si el recurso se elimina, wPtr1 expira.
Para evitar la dependencia circular:
shard_ptr<A> <----| shared_ptr<B> <------
^ | ^ |
| | | |
| | | |
| | | |
| | | |
class A | class B |
| | | |
| ------------ |
| |
-------------------------------------
Ahora, si hacemos el shared_ptr de la clase B y A, el use_count de ambos punteros es dos.
Cuando shared_ptr sale del alcance, el recuento sigue siendo 1 y, por lo tanto, los objetos A y B no se eliminan.
class B;
class A
{
shared_ptr<B> sP1; // use weak_ptr instead to avoid CD
public:
A() { cout << "A()" << endl; }
~A() { cout << "~A()" << endl; }
void setShared(shared_ptr<B>& p)
{
sP1 = p;
}
};
class B
{
shared_ptr<A> sP1;
public:
B() { cout << "B()" << endl; }
~B() { cout << "~B()" << endl; }
void setShared(shared_ptr<A>& p)
{
sP1 = p;
}
};
int main()
{
shared_ptr<A> aPtr(new A);
shared_ptr<B> bPtr(new B);
aPtr->setShared(bPtr);
bPtr->setShared(aPtr);
return 0;
}
salida:
A()
B()
Como podemos ver en la salida, el puntero A y B nunca se eliminan y, por lo tanto, la pérdida de memoria.
Para evitar este problema, simplemente use weak_ptr en la clase A en lugar de shared_ptr, lo que tiene más sentido.
Lo veo std::weak_ptr<T>
como un identificador para std::shared_ptr<T>
: me permite obtener el std::shared_ptr<T>
si aún existe, pero no extenderá su vida útil. Existen varios escenarios en los que este punto de vista es útil:
// Some sort of image; very expensive to create.
std::shared_ptr< Texture > texture;
// A Widget should be able to quickly get a handle to a Texture. On the
// other hand, I don't want to keep Textures around just because a widget
// may need it.
struct Widget {
std::weak_ptr< Texture > texture_handle;
void render() {
if (auto texture = texture_handle.get(); texture) {
// do stuff with texture. Warning: `texture`
// is now extending the lifetime because it
// is a std::shared_ptr< Texture >.
} else {
// gracefully degrade; there's no texture.
}
}
};
Otro escenario importante es romper los ciclos en las estructuras de datos.
// Asking for trouble because a node owns the next node, and the next node owns
// the previous node: memory leak; no destructors automatically called.
struct Node {
std::shared_ptr< Node > next;
std::shared_ptr< Node > prev;
};
// Asking for trouble because a parent owns its children and children own their
// parents: memory leak; no destructors automatically called.
struct Node {
std::shared_ptr< Node > parent;
std::shared_ptr< Node > left_child;
std::shared_ptr< Node > right_child;
};
// Better: break dependencies using a std::weak_ptr (but not best way to do it;
// see Herb Sutter's talk).
struct Node {
std::shared_ptr< Node > next;
std::weak_ptr< Node > prev;
};
// Better: break dependencies using a std::weak_ptr (but not best way to do it;
// see Herb Sutter's talk).
struct Node {
std::weak_ptr< Node > parent;
std::shared_ptr< Node > left_child;
std::shared_ptr< Node > right_child;
};
Herb Sutter tiene una excelente charla que explica el mejor uso de las características del lenguaje (en este caso, punteros inteligentes) para garantizar la libertad de fuga de forma predeterminada (es decir: todo encaja en su lugar por construcción; difícilmente puede arruinarlo). Es una visita obligada.