Comencé 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?
c++11
shared-ptr
weak-ptr
c++-faq
artm
fuente
fuente
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.
fuente
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 permitirstd::shared_ptr
administrar los datos y suministrarlosstd::weak_ptr
a los usuarios de los datos, los usuarios pueden verificar la validez de los datos llamandoexpired()
olock()
.No podría hacer esto
std::shared_ptr
solo, porque todas lasstd::shared_ptr
instancias comparten la propiedad de los datos que no se eliminan antes de que se eliminen todas las instanciasstd::shared_ptr
. Aquí hay un ejemplo de cómo verificar el puntero colgante usandolock()
:fuente
std::weak_ptr::lock
crea una nuevastd::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
yMember
objetos.Obviamente es una relación: el
Team
objeto tendrá punteros a suMembers
. Y es probable que los miembros también tengan un puntero hacia atrás a suTeam
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 utilizarshared_ptr
y la "propiedad" utilizar unaweak_ptr
a su padre, y lo convierten temporalmente ashared_ptr
cuando se necesita acceder a su matriz.Almacene un ptr débil:
luego úselo cuando sea necesario
fuente
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 unshared_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 aystd::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.fuente
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, utilizandostd::bind
o capturas lambda.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.fuente
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 loshared_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.fuente
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í:fuente
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.
fuente
Además de los otros casos de uso válidos ya mencionados,
std::weak_ptr
es una herramienta increíble en un entorno multiproceso, porquestd::shared_ptr
en conjunto constd::weak_ptr
es seguro contra punteros colgantes - en oposición astd::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.)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 usarstd::shared_ptr
en conjunto constd::weak_ptr
.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.
fuente
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.
fuente
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.
fuente
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:
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:
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.
salida:
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.
fuente
Lo veo
std::weak_ptr<T>
como un identificador parastd::shared_ptr<T>
: me permite obtener elstd::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:Otro escenario importante es romper los ciclos en las estructuras de datos.
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.
fuente