Cargando...

Introducción a los smart pointers

Primeros pasos con unique_ptr, shared_ptr y weak_ptr para gestionar vidas útiles.

En C++, la gestión dinámica de memoria se realiza tradicionalmente con new y delete. Sin embargo, la gestión manual de la memoria puede causar problemas como fugas de memoria, doble eliminación o punteros colgantes (dangling pointers). El C++ moderno (C++11 y posteriores) introdujo el concepto de punteros inteligentes (smart pointers) para resolver estos problemas. Los punteros inteligentes automatizan la gestión de memoria y eliminan la mayoría de estos errores.


1. ¿Qué es un puntero inteligente?

Un puntero inteligente es una clase especial de C++ que gestiona la vida útil de un objeto. Se comporta como un puntero normal, pero controla automáticamente el ciclo de vida del objeto (creación y destrucción). Esto ayuda a prevenir fugas de memoria.


#include <iostream>
#include <memory>
using namespace std;

int main() {
    unique_ptr<int> ptr = make_unique<int>(42);
    cout << *ptr << endl; // 42
}

Existen tres tipos principales de punteros inteligentes: unique_ptr, shared_ptr y weak_ptr. Todos se encuentran en la cabecera <memory>.


2. unique_ptr

std::unique_ptr es un puntero cuya propiedad pertenece exclusivamente a un solo objeto. Solo un unique_ptr puede poseer un objeto a la vez. No puede copiarse, pero sí puede moverse (con std::move()).


#include <iostream>
#include <memory>
using namespace std;

int main() {
    unique_ptr<int> p1 = make_unique<int>(10);
    cout << *p1 << endl;

    // unique_ptr no puede copiarse:
    // unique_ptr<int> p2 = p1; // ERROR!

    // Pero puede moverse:
    unique_ptr<int> p2 = move(p1);
    cout << *p2 << endl;
}

Ventajas:

Desventaja:
unique_ptr no puede copiarse, por lo que no es adecuado cuando se necesita compartir un mismo objeto.


3. shared_ptr

std::shared_ptr permite que varios punteros compartan la propiedad del mismo objeto. La vida útil del objeto depende del número de propietarios (contador de referencias). Cuando el último shared_ptr se destruye, el objeto se elimina automáticamente.


#include <iostream>
#include <memory>
using namespace std;

int main() {
    shared_ptr<int> p1 = make_shared<int>(100);
    shared_ptr<int> p2 = p1; // Propiedad compartida

    cout << "p1: " << *p1 << ", p2: " << *p2 << endl;
    cout << "Contador de referencias: " << p1.use_count() << endl; // 2
}

Ventajas:

Desventajas:


4. weak_ptr

std::weak_ptr es una referencia “débil” (non-owning) a un objeto gestionado por un shared_ptr. No prolonga la vida del objeto; simplemente lo observa. Esto evita los problemas de referencias circulares.


#include <iostream>
#include <memory>
using namespace std;

int main() {
    shared_ptr<int> sp = make_shared<int>(50);
    weak_ptr<int> wp = sp;

    cout << "Contador de referencias: " << sp.use_count() << endl;

    if (auto ptr = wp.lock()) {
        cout << "Valor: " << *ptr << endl;
    }
}

wp.lock() devuelve un shared_ptr válido si el objeto aún existe; si ya fue destruido, devuelve nullptr. Esto permite un acceso seguro.


5. Problema de referencias circulares

Si dos shared_ptr se referencian mutuamente, el contador de referencias nunca llega a cero, y la memoria no se libera. Este problema suele ocurrir en estructuras enlazadas o jerárquicas.


#include <memory>
using namespace std;

struct Node {
    shared_ptr<Node> next;
    // ERROR: Referencia circular
};

int main() {
    shared_ptr<Node> n1 = make_shared<Node>();
    shared_ptr<Node> n2 = make_shared<Node>();
    n1->next = n2;
    n2->next = n1; // Bucle infinito
}

La solución es usar un weak_ptr:


struct Node {
    weak_ptr<Node> next; // Evita el bucle de referencia
};

6. Uso de punteros inteligentes en funciones

Los punteros inteligentes pueden pasarse a funciones igual que los punteros normales. Sin embargo, se debe tener en cuenta el modelo de propiedad.


void Imprimir(shared_ptr<int> p) {
    cout << *p << endl;
}

int main() {
    auto ptr = make_shared<int>(77);
    Imprimir(ptr); // Uso compartido seguro
}

Si se quiere transferir la propiedad, debe usarse unique_ptr con std::move().


void Recibir(unique_ptr<int> p) {
    cout << *p << endl;
}

int main() {
    auto p = make_unique<int>(88);
    Recibir(move(p)); // p ahora es nullptr
}

7. Ventajas de la gestión de memoria con punteros inteligentes


8. Tabla comparativa

Tipo Propiedad Copiable Contador de referencias Uso
unique_ptr Única No No Objetos con un solo propietario
shared_ptr Compartida Objetos con varios propietarios
weak_ptr Sin propiedad Depende de un shared_ptr Evitar referencias circulares

9. TL;DR

  • unique_ptr → propiedad única, no copiable, movible.
  • shared_ptr → propiedad compartida con contador de referencias.
  • weak_ptr → observa un objeto compartido sin ser su propietario.
  • Los punteros inteligentes reducen significativamente las fugas de memoria.
  • Usa weak_ptr para evitar referencias circulares.
  • Todos los ejemplos se pueden ejecutar en Visual Studio 2022 o GCC 11+.

Artículos relacionados