¿Cuándo es útil std :: weak_ptr?

Respuestas:

231

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.

David Schwartz
fuente
8
Entonces, ¿std :: wake_ptr solo puede apuntar a donde apunta otro puntero y apunta a nullptr cuando el objeto señalado ya no es eliminado / señalado por ningún otro puntero?
27
@RM: Básicamente, sí. Cuando tiene un puntero débil, puede intentar promocionarlo a un puntero fuerte. Si ese objeto todavía existe (porque al menos un puntero fuerte todavía existe) esa operación tiene éxito y le da un puntero fuerte. Si ese objeto no existe (porque todos los punteros fuertes desaparecieron), entonces esa operación falla (y típicamente reaccionas tirando el puntero débil).
David Schwartz
12
Mientras que un puntero fuerte mantiene vivo un objeto, un débil_ptr puede mirarlo ... sin interferir con el tiempo de vida del objeto.
The Vivandiere
3
Otro ejemplo, que he usado al menos algunas veces, es cuando implementamos observadores, a veces es conveniente que el sujeto mantenga una lista de punteros débiles y haga su propia limpieza de la lista. Se ahorra un poco de esfuerzo al eliminar explícitamente a los observadores cuando se eliminan, y lo más importante es que no es necesario tener información sobre los temas disponibles al destruir observadores, lo que generalmente simplifica mucho las cosas.
Jason C
3
Espera, ¿qué hay de malo en que el caché contenga un shared_ptr y simplemente lo elimine de su lista cuando debería borrarse de la memoria? Todos los usuarios mantendrán un shared_ptr de todos modos y el recurso en caché se borrará tan pronto como todos los usuarios hayan terminado con él.
rubenvb
299

std::weak_ptres 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_ptradministrar los datos y suministrarlos std::weak_ptra 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_ptrsolo, porque todas las std::shared_ptrinstancias 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";
}
sunefred
fuente
1
Ok, es como si configurara localmente un puntero (propietario) a nulo (eliminar memoria), todos los demás punteros (débiles) a la misma memoria también están configurados a nulo
Pat-Laugh
std::weak_ptr::lockcrea una nueva std::shared_ptrque comparte la propiedad del objeto administrado.
Sahib Yar
129

Otra respuesta, con suerte más simple. (para compañeros de googlers)

Supongamos que tienes Teamy Memberobjetos.

Obviamente es una relación: el Teamobjeto tendrá punteros a su Members. Y es probable que los miembros también tengan un puntero hacia atrás a su Teamobjeto.

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_ptry la "propiedad" utilizar una weak_ptra su padre, y lo convierten temporalmente a shared_ptrcuando 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
Offirmo
fuente
1
¿Cómo es esto una pérdida de memoria? Si el equipo se destruye, destruirá a sus miembros, por lo tanto, el recuento de referencias shared_ptr será 0 y también se destruirá.
Paul
44
@paulm Team no destruirá "sus" miembros. El objetivo shared_ptres 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_ptrpara empezar.
Offirmo
1
No los destruirá, pero su shared_ptr se saldrá de su alcance, disminuirá el use_count, por lo tanto, en este punto use_count es 0 y entonces shared_ptr eliminará lo que apunta.
Paul
2
@paulm Tienes razón. Pero dado que, en este ejemplo, el equipo también es shared_ptrreferenciado por sus "miembros del equipo", ¿cuándo será destruido? Lo que está describiendo es un caso en el que no hay bucle.
Offirmo
14
No es tan malo, creo. Si un miembro puede pertenecer a muchos equipos, usar una referencia no funcionará.
Mazyod
22

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.

Kerrek SB
fuente
44
: Suena como un buen ejemplo, pero ¿puedes elaborar tu ejemplo un poco más? Estoy pensando que cuando una tarea finaliza, ya debería haberse eliminado de std :: vector <std :: weak_ptr <Task>> sin una verificación periódica. Así que no estoy seguro si std :: vector <std :: weak_ptr <>> es muy útil aquí.
Gob00st
Comentario similar con colas: digamos que tiene objetos y los pone en cola para algún recurso, los objetos podrían eliminarse mientras espera. Por lo tanto, si hace una cola débil_ptrs, no tiene que molestarse en eliminar entradas de la cola. Weak_ptrs se invalidará y luego se descartará cuando esté incluido.
zzz777
1
@ zzz777: La lógica que invalida los objetos puede no ser consciente de la existencia de la cola o el vector de los observadores. Entonces, el observador realiza un circuito separado sobre los punteros débiles, actuando sobre los que aún están vivos y retirando a los muertos del contenedor ...
Kerrek SB
1
@KerekSB: sí, y en caso de cola, ni siquiera tiene que hacer un bucle separado; entonces el recurso está disponible, descarte los puntos débiles caducados (si corresponde) hasta que obtenga uno válido (si corresponde).
zzz777
También podría hacer que los hilos se eliminen de la colección, pero eso crearía una dependencia y requeriría un bloqueo.
curioso
16

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_ptrobjeto de controlador asíncrono, utilizando std::bindo 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.

Emile Cormier
fuente
¿Por qué tardó tanto en encontrar esta respuesta ... PD: no estás usando tu captura dethis
Orwellophile
@Orwellophile arreglado. Fuerza de hábito cuando se usa el self = shared_from_this()idioma cuando el controlador invoca métodos dentro de la misma clase.
Emile Cormier
16

shared_ptr : contiene el objeto real.

weak_ptr : se usa lockpara conectarse con el propietario real o, de lo shared_ptrcontrario, devuelve un NULL .

ptr débil

En términos generales, el weak_ptrpapel 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.

Saurav Sahu
fuente
14

weak_ptrTambié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());
Galleta
fuente
13

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:

  • Punteros crudos:
    • Puntero sin procesar [es decir SomeClass* ptrToSomeClass = new SomeClass();]
  • Punteros inteligentes:
    • Punteros únicos [ie
      std::unique_ptr<SomeClass> uniquePtrToSomeClass ( new SomeClass() );
      ]
    • Punteros compartidos [es decir
      std::shared_ptr<SomeClass> sharedPtrToSomeClass ( new SomeClass() );
      ]
    • Punteros Débiles [ie
      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:

  • Está ocupado y tiene reuniones superpuestas: Reunión A y Reunión B
  • 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 continúa después de que termine la reunión A, se unirá
  • Los siguientes dos escenarios podrían desarrollarse:
    • La reunión A termina y la reunión B aún continúa, así que únete
    • La reunión A finaliza y la reunión B también ha finalizado, por lo que no puede unirse

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.

Jeremy
fuente
6

Además de los otros casos de uso válidos ya mencionados, std::weak_ptres una herramienta increíble en un entorno multiproceso, porque

  • No posee el objeto y, por lo tanto, no puede obstaculizar la eliminación en un hilo diferente
  • std::shared_ptren conjunto con std::weak_ptres seguro contra punteros colgantes - en oposición a std::unique_ptren conjunto con punteros sin procesar
  • std::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_imageDatascampo. 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 imagesToLoadserí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_ptren 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.

usuario2328447
fuente
2

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.

MYLOGOS
fuente
" para romper referencias circulares " ¿cómo?
curioso
2

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.

ashutosh
fuente
¿Cómo puede una referencia débil lidiar con una dependencia circular?
curioso
1
@curiousguy, un niño emplea una referencia débil al padre, luego el padre puede ser desasignado cuando no hay referencias compartidas (fuertes) que lo apunten. Por lo tanto, al acceder al padre a través del hijo, la referencia débil tiene que probarse para ver si el padre todavía está disponible. Alternativamente, para evitar esa condición adicional, un mecanismo de rastreo de referencia circular (ya sea barrido de marca o sondeo en decrementos de recuento, los cuales tienen un mal rendimiento asintótico) puede romper las referencias compartidas circulares cuando las únicas referencias compartidas para el padre y el hijo son de cada otro.
Shelby Moore III
@ShelbyMooreIII " tiene que probarse para ver si el padre todavía está disponible " ¡sí, y usted debe poder reaccionar correctamente ante el caso no disponible! Lo que no ocurre con una referencia real (es decir, fuerte). Lo que significa que la referencia débil no es una caída en el reemplazo: requiere un cambio en la lógica.
curioso
2
@curiousguy no preguntaste "¿Cómo puede un weak_ptracuerdo con una dependencia circular sin cambios en la lógica del programa como un reemplazo directo parashared_ptr ?" :-)
Shelby Moore III
2

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.

Swapnil
fuente
2

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.

Escualo
fuente