auto_ptr, unique_ptr, shared_ptr y débil_ptr

Requisito previo: las bibliotecas Smart Pointers
C++ proporcionan implementaciones de punteros inteligentes en los siguientes tipos:

  • auto_ptr
  • único_ptr
  • ptr_compartido
  • débil_ptr

Todos ellos se declaran en un archivo de encabezado de memoria.

auto_ptr

Esta plantilla de clase está obsoleta a partir de C++11. unique_ptr es una nueva instalación con una funcionalidad similar, pero con seguridad mejorada.
auto_ptr es un puntero inteligente que administra un objeto obtenido a través de una nueva expresión y elimina ese objeto cuando se destruye auto_ptr.
Cuando se describe un objeto usando la clase auto_ptr, almacena un puntero a un solo objeto asignado que garantiza que cuando sale del alcance, el objeto al que apunta debe destruirse automáticamente. Se basa en el modelo de propiedad exclusiva, es decir, dos punteros del mismo tipo no pueden apuntar al mismo recurso al mismo tiempo. Como se muestra en el siguiente programa, la copia o asignación de punteros cambia la propiedad, es decir, el puntero de origen tiene que dar propiedad al puntero de destino.

Auto pointer in C++

// C++ program to illustrate the use of auto_ptr
#include <iostream>
#include <memory>
using namespace std;
  
class A {
public:
    void show() { cout << "A::show()" << endl; }
};
  
int main()
{
    // p1 is an auto_ptr of type A
    auto_ptr<A> p1(new A);
    p1->show();
  
    // returns the memory address of p1
    cout << p1.get() << endl;
  
    // copy constructor called, this makes p1 empty.
    auto_ptr<A> p2(p1);
    p2->show();
  
    // p1 is empty now
    cout << p1.get() << endl;
  
    // p1 gets copied in p2
    cout << p2.get() << endl;
  
    return 0;
}

Producción:

A::show()
0x1b42c20
A::show()
0          
0x1b42c20

El constructor de copia y el operador de asignación de auto_ptr en realidad no copian el puntero almacenado, sino que lo transfieren, dejando vacío el primer objeto auto_ptr. Esta fue una forma de implementar la propiedad estricta para que solo un objeto auto_ptr pueda poseer el puntero en un momento dado, es decir, auto_ptr no debe usarse donde se necesita la semántica de copia.

¿Por qué está obsoleto auto_ptr?
Toma posesión del puntero de manera que dos punteros no deben contener el mismo objeto. La asignación transfiere la propiedad y restablece el puntero automático rvalue a un puntero nulo. Por lo tanto, no se pueden usar dentro de los contenedores STL debido a la incapacidad de copia antes mencionada.

único_ptr

std::unique_ptr fue desarrollado en C++11 como reemplazo de std::auto_ptr.
unique_ptr es una nueva instalación con una funcionalidad similar, pero con seguridad mejorada (sin asignaciones de copias falsas), funciones adicionales (eliminadores) y soporte para arreglos. Es un contenedor para punteros en bruto. Previene explícitamente la copia de su puntero contenido como sucedería con una asignación normal, es decir, permite exactamente un propietario del puntero subyacente.
Por lo tanto, cuando se usa unique_ptr, solo puede haber como máximo un unique_ptr en cualquier recurso y cuando ese unique_ptr se destruye, el recurso se reclama automáticamente. Además, dado que solo puede haber un único_ptr para cualquier recurso, cualquier intento de hacer una copia de único_ptr provocará un error de tiempo de compilación.

 unique_ptr<A> ptr1 (new A);

 // Error: can't copy unique_ptr
 unique_ptr<A> ptr2 = ptr1;    

Pero, unique_ptr se puede mover usando la nueva semántica de movimiento, es decir, usando la función std::move() para transferir la propiedad del puntero contenido a otro unique_ptr.

// Works, resource now stored in ptr2
unique_ptr<A> ptr2 = move(ptr1); 

Por lo tanto, es mejor usar unique_ptr cuando queremos un puntero único a un objeto que se recuperará cuando se destruya ese puntero único.

// C++ program to illustrate the use of unique_ptr
#include <iostream>
#include <memory>
using namespace std;
  
class A {
public:
    void show()
    {
        cout << "A::show()" << endl;
    }
};
  
int main()
{
    unique_ptr<A> p1(new A);
    p1->show();
  
    // returns the memory address of p1
    cout << p1.get() << endl;
  
    // transfers ownership to p2
    unique_ptr<A> p2 = move(p1);
    p2->show();
    cout << p1.get() << endl;
    cout << p2.get() << endl;
  
    // transfers ownership to p3
    unique_ptr<A> p3 = move(p2);
    p3->show();
    cout << p1.get() << endl;
    cout << p2.get() << endl;
    cout << p3.get() << endl;
  
    return 0;
}

Producción:

A::show()
0x1c4ac20
A::show()
0          // NULL
0x1c4ac20
A::show()
0          // NULL
0          // NULL
0x1c4ac20

El siguiente código devuelve un recurso y, si no capturamos explícitamente el valor devuelto, el recurso se limpiará. Si lo hacemos, entonces tenemos la propiedad exclusiva de ese recurso. De esta forma, podemos pensar en unique_ptr como un reemplazo mejor y más seguro de auto_ptr.

unique_ptr<A> fun()
{
    unique_ptr<A> ptr(new A);

    /* ...
       ... */

    return ptr;
}

¿Cuándo usar unique_ptr?
Use unique_ptr cuando desee tener una propiedad única (exclusiva) del recurso. Solo un único_ptr puede apuntar a un recurso. Dado que puede haber un único_ptr para un solo recurso, no es posible copiar un único_ptr a otro.

ptr_compartido

Un shared_ptr es un contenedor para punteros sin procesar. Es un modelo de propiedad de recuento de referencias, es decir, mantiene el recuento de referencias de su puntero contenido en cooperación con todas las copias de shared_ptr. Entonces, el contador se incrementa cada vez que un nuevo puntero apunta al recurso y se decrementa cuando se llama al destructor del objeto.

Recuento de referencias: es una técnica para almacenar el número de referencias, punteros o identificadores de un recurso, como un objeto, bloque de memoria, espacio en disco u otros recursos.

Un objeto al que hace referencia el puntero bruto contenido no se destruirá hasta que el recuento de referencias sea mayor que cero, es decir, hasta que se hayan eliminado todas las copias de shared_ptr.
Por lo tanto, deberíamos usar shared_ptr cuando queramos asignar un puntero sin formato a varios propietarios.

// C++ program to demonstrate shared_ptr
#include <iostream>
#include <memory>
using namespace std;
  
class A {
public:
    void show()
    {
        cout << "A::show()" << endl;
    }
};
  
int main()
{
    shared_ptr<A> p1(new A);
    cout << p1.get() << endl;
    p1->show();
    shared_ptr<A> p2(p1);
    p2->show();
    cout << p1.get() << endl;
    cout << p2.get() << endl;
  
    // Returns the number of shared_ptr objects
    // referring to the same managed object.
    cout << p1.use_count() << endl;
    cout << p2.use_count() << endl;
  
    // Relinquishes ownership of p1 on the object
    // and pointer becomes NULL
    p1.reset();
    cout << p1.get() << endl;
    cout << p2.use_count() << endl;
    cout << p2.get() << endl;
  
    return 0;
}

Producción:

0x1c41c20
A::show()
A::show()
0x1c41c20
0x1c41c20
2
2
0          // NULL
1
0x1c41c20

¿Cuándo usar shared_ptr?
Utilice shared_ptr si desea compartir la propiedad de un recurso. Muchos shared_ptr pueden apuntar a un solo recurso. shared_ptr mantiene el recuento de referencias para esta propuesta. cuando todos los shared_ptr que apuntan al recurso quedan fuera del alcance, el recurso se destruye.

débil_ptr

Se crea un débil_ptr como una copia de shared_ptr. Proporciona acceso a un objeto que es propiedad de una o más instancias shared_ptr pero no participa en el recuento de referencias. La existencia o destrucción de débil_ptr no tiene ningún efecto sobre shared_ptr o sus otras copias. En algunos casos es necesario romper referencias circulares entre instancias shared_ptr.


Dependencia cíclica (problemas con shared_ptr):
Consideremos un escenario donde tenemos dos clases A y B, ambas tienen punteros a otras clases. Entonces, siempre es como si A apuntara a B y B apuntara a A. Por lo tanto, use_count nunca llegará a cero y nunca se eliminarán.

Circular reference for weak pointer

Esta es la razón por la que usamos punteros débiles (weak_ptr) ya que no se cuentan como referencia. Por lo tanto, la clase en la que se declara débil_ptr no tiene una fortaleza, es decir, la propiedad no se comparte, pero pueden tener acceso a estos objetos.

Circular reference for weak pointer

Por lo tanto, en el caso de shared_ptr debido a la dependencia cíclica, use_count nunca llega a cero, lo que se evita usando débil_ptr, lo que elimina este problema al declarar A_ptr como débil_ptr, por lo tanto, la clase A no es propietaria, solo tiene acceso a él y también debemos verificar el validez del objeto, ya que puede quedar fuera del alcance. En general, es un problema de diseño.

¿Cuándo usar débil_ptr?
Cuando desee hacer referencia a su objeto desde varios lugares, para aquellas referencias para las que está bien ignorar y desasignar (para que solo noten que el objeto se ha ido cuando intenta eliminar la referencia).

Referencia:
https://www.quora.com/When-should-I-use-shared_ptr-and-unique_ptr-in-C++-and-what-are-they-good-for

Este artículo es una contribución de Himanshu Gupta (Bagri) . Si te gusta GeeksforGeeks y te gustaría contribuir, también puedes escribir un artículo usando write.geeksforgeeks.org o enviar tu artículo por correo a review-team@geeksforgeeks.org. Vea su artículo que aparece en la página principal de GeeksforGeeks y ayude a otros Geeks.

Escriba comentarios si encuentra algo incorrecto o si desea compartir más información sobre el tema tratado anteriormente.

Publicación traducida automáticamente

Artículo escrito por GeeksforGeeks-1 y traducido por Barcelona Geeks. The original can be accessed here. Licence: CCBY-SA

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *