¿Cuándo es útil std::weak ptr?


Empecé a estudiar punteros inteligentes de C++11 y no veo ningún uso útil de std::weak_ptr. ¿Puede alguien decirme cuándo std::weak_ptr es útil/necesario?

Author: curiousguy, 2012-08-20

11 answers

Un buen ejemplo sería una caché.

Para los objetos a los que se ha accedido recientemente, desea mantenerlos en la memoria, de modo que mantenga un puntero fuerte hacia ellos. Periódicamente, escanea la caché y decide a qué objetos no se ha accedido recientemente. No necesitas guardarlos en la memoria, así que deshazte del puntero fuerte.

Pero, ¿qué pasa si ese objeto está en uso y algún otro código tiene un puntero fuerte hacia él? Si la caché se deshace de su único puntero al objeto, nunca podrá encontrarlo nuevo. Por lo tanto, la caché mantiene un puntero débil a los objetos que necesita encontrar si se quedan en la memoria.

Esto es exactamente lo que hace un puntero débil allows le permite localizar un objeto si todavía está alrededor, pero no lo mantiene alrededor si nada más lo necesita.

 159
Author: David Schwartz,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-08-19 23:06:28

std::weak_ptr es una muy buena manera de resolver el problema del puntero colgando . Con solo usar punteros raw es imposible saber si los datos referenciados han sido desasignados o no. En su lugar, al permitir que un 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 puede hacer esto solo con std::shared_ptr, porque todas las instancias std::shared_ptr comparten la propiedad de los datos que no se eliminan antes de todas las instancias de std::shared_ptr se suprimen. Aquí hay un ejemplo de cómo comprobar si hay un 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";
}
 210
Author: sunefred,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-03-20 21:19:05

Otra respuesta, ojalá más simple. (para compañeros de Google)

Supongamos que tienes Team y Member objetos.

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

Entonces tienes un ciclo de dependencia. Si utiliza shared_ptr, los objetos ya no se liberarán automáticamente cuando abandone la referencia en ellos, porque se refieren entre sí de manera cíclica. Esto es una fuga de memoria.

Puedes romper esto usando weak_ptr. El" propietario " típicamente usa shared_ptr y el "propietario" usa un weak_ptr a su padre, y lo convierte temporalmente a shared_ptr cuando necesita acceso a su padre.

Almacene un rpp débil:

weak_ptr<Parent> parentWeakPtr_ = parentSharedPtr; // automatic conversion to weak from shared

Luego úsalo 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
 84
Author: Offirmo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-08-09 11:44:15

Aquí hay un ejemplo, que me dio @jleahy: Supongamos que tiene una colección de tareas, ejecutadas asincrónicamente 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 un std::vector<std::weak_ptr<Task>> y 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 sigue viva haciendo un puntero compartido desde el puntero débil y usando eso puntero compartido, siempre que no sea nulo.

 17
Author: Kerrek SB,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-08-19 23:07:41

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 tener este aspecto:

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());
 12
Author: Cookie,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-06-05 12:55:53

Shared_ptr : contiene el objeto real.

Weak_ptr : usa lock para conectarse al propietario real o devuelve NULL de lo contrario.

débil ptr

En términos generales, weak_ptr el papel es similar al papel de agencia de vivienda. Sin agentes, para conseguir una casa en alquiler puede que tengamos que comprobar casas al azar en la ciudad. Los agentes se aseguran de que visitemos solo aquellas casas que son todavía accesibles y disponibles para alquiler.

 9
Author: Saurav Sahu,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-03 07:33:49

Son útiles con Boost.Asio cuando no se garantiza que un objeto de destino todavía existe cuando se invoca un controlador asíncrono. El truco es enlazar un weak_ptr en el objeto manejador asíncrono, usando 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 modismo self = shared_from_this() a menudo visto en Boost.Ejemplos Asio, donde un manejador asíncrono pendiente no prolongará la vida útil del objeto de destino, pero sigue siendo seguro si el objeto de destino se elimina.

 9
Author: Emile Cormier,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-08-09 14:26:42

Al usar punteros es importante entender 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 crudo [es decir, SomeClass* ptrToSomeClass = new SomeClass();]
  • punteros Inteligentes:
    • Punteros únicos [ i. e. std::unique_ptr<SomeClass> uniquePtrToSomeClass ( new SomeClass() ); ]
    • Punteros compartidos [es decir, std::shared_ptr<SomeClass> sharedPtrToSomeClass ( new SomeClass() );]
    • Punteros débiles [ i. e. std::weak_ptr<SomeClass> weakPtrToSomeWeakOrSharedPtr ( weakOrSharedPtr ); ]

Punteros crudos (a veces conocidos como "punteros heredados", o "punteros C") proporcionan un comportamiento de puntero 'básico' y son una fuente común de errores y fugas de memoria. Los punteros raw no proporcionan ningún medio para realizar un seguimiento de la propiedad del recurso y los desarrolladores deben llamar 'eliminar' manualmente para asegurarse de que no están creando una fuga de memoria. Esto se vuelve difícil si el recurso se comparte, ya que puede ser difícil saber si algún objeto todavía está apuntando al recurso. Por estas razones, los punteros crudos generalmente deben ser se evita y solo se utiliza en secciones críticas de rendimiento del código con alcance limitado.

Los punteros únicos son un puntero inteligente básico que 'posee' el puntero raw subyacente al recurso y es responsable de llamar a delete y liberar la memoria asignada una vez que el objeto que 'posee' el puntero único sale de alcance. El nombre ' unique 'se refiere al hecho de que solo un objeto puede' poseer ' el puntero único en un punto dado en el tiempo. La propiedad puede transferirse a otro objeto a través de el comando mover, pero un puntero único nunca se puede copiar o compartir. Por estas razones, 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 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. Como puntero único, los punteros compartidos son responsables de liberar la memoria asignada una vez que todos los objetos han terminado apuntando al recurso. Logra esto con una técnica llamada conteo de referencia. Cada vez que un nuevo objeto toma posesión del puntero compartido, el recuento de referencia se incrementa en uno. Del mismo modo, cuando un objeto sale del ámbito o deja de apuntar al recurso, el recuento de referencias se decrementa en uno. Cuando el recuento de referencia llega a cero, se libera la memoria asignada. Para por estas razones, los punteros compartidos son un tipo muy poderoso de puntero inteligente que se debe usar en cualquier momento que varios objetos necesiten apuntar al mismo recurso.

Finalmente, los punteros débiles son otro tipo de puntero inteligente que, en lugar de apuntar a un recurso directamente, apuntan a otro puntero (débil o compartido). Los punteros débiles no pueden acceder a un objeto directamente, pero pueden saber si el objeto aún existe o si ha caducado. Un puntero débil se puede convertir temporalmente en un puntero inteligente puntero para acceder al objeto apuntado (siempre que todavía exista). Para ilustrarlo, considere el siguiente ejemplo:

  • Usted 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 dices a tu compañero de trabajo que si la Reunión B sigue en marcha después de que termine la Reunión A, te unirás
  • Los siguientes dos escenarios podrían jugar:
    • La Reunión A termina y la Reunión B sigue, así que te unes
    • La reunión A termina y la Reunión B también ha terminado, así 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 terminar sin usted, y no sabe si terminó o no a menos que marque. Si no ha terminado, puede unirse y participar, de lo contrario, no puede. Esto es diferente a tener un puntero compartido a la Reunión B porque entonces sería un "propietario" tanto en la Reunión A como en la Reunión B (participar en ambos 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 escenario en el que dos objetos necesitan apuntar el uno al otro (también conocido como una referencia circular). Con punteros compartidos, ninguno de los objetos puede ser liberado porque todavía están' fuertemente ' apuntados por el otro objeto. Con punteros débiles, los objetos pueden ser accedidos cuando sea necesario, y liberados cuando ya no necesitan existir.

 7
Author: Jeremy,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-12-08 03:52:16

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 que es administrado por std:: shared_ptr. Debe ser convertido a std:: shared_ptr para poder acceder al objeto referenciado.

Std:: weak_ptr models propiedad temporal: cuando se necesita acceder a un objeto 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 en 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 destruye.

Además, std::weak_ptr se usa para romper referencias circulares de std::shared_ptr.

 1
Author: MYLOGOS,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-05-12 11:57:47

Hay un inconveniente del puntero compartido: shared_pointer no puede manejar la dependencia del ciclo padre-hijo. Significa que si la clase padre usa el objeto de la clase hijo usando un puntero compartido, en el mismo archivo si la clase hijo usa el objeto de la clase padre. El puntero compartido no podrá destruir todos los objetos, incluso el puntero compartido no está llamando al destructor en el escenario de dependencia de ciclo. básicamente, el puntero compartido no admite el mecanismo de recuento de referencias.

Esto inconveniente que podemos superar usando weak_pointer.

 1
Author: ashutosh,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-07-02 19:05:07

Cuando no queremos poseer el objeto:

Ex:

class A
{
    shared_ptr<int> sPtr1;
    weak_ptr<int> wPtr1;
}

En la clase anterior wPtr1 no posee el recurso apuntado por wPtr1. Si el recurso se elimina, entonces wPtr1 ha expirado.

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 puntero es dos.

Cuando el shared_ptr sale od scope, el conteo sigue siendo 1 y, por lo tanto, el objeto A y B no obtiene eliminar.

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, se pierde memoria.

Para evitar este problema, simplemente use weak_ptr en la clase A en lugar de shared_ptr, lo que tiene más sentido.

 0
Author: Swapnil,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-08-12 08:04:16