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:
- Gestión automática de memoria
- Ligero y eficiente
- No requiere llamar manualmente a
delete
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:
- Un objeto puede compartirse entre varios punteros
- No se requiere liberar memoria manualmente
- Mayor consumo de memoria y sobrecarga por el contador de referencias
- Puede causar fugas en casos de referencias circulares
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
- No se necesita llamar a
deletemanualmente. - Previenen fugas de memoria.
- Implementan el principio RAII (Resource Acquisition Is Initialization).
- Liberan memoria automáticamente en caso de excepciones.
weak_ptrevita referencias circulares.
8. Tabla comparativa
| Tipo | Propiedad | Copiable | Contador de referencias | Uso |
|---|---|---|---|---|
unique_ptr |
Única | No | No | Objetos con un solo propietario |
shared_ptr |
Compartida | Sí | Sí | Objetos con varios propietarios |
weak_ptr |
Sin propiedad | Sí | 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_ptrpara evitar referencias circulares. - Todos los ejemplos se pueden ejecutar en Visual Studio 2022 o GCC 11+.