Punteros inteligentes en C++
Parte de la sección Programación Orientada a Objetos del Journey de C++ de Coddy — lección 15 de 104.
Los punteros inteligentes (smart pointers) son clases envolventes que gestionan automáticamente la memoria del montón (heap) por ti. Eliminan la necesidad de realizar llamadas manuales a delete al liberar la memoria cuando el puntero sale de su ámbito.
Incluya el encabezado <memory> para usar punteros inteligentes.
unique_ptr - propiedad exclusiva. Solo un puntero puede ser el propietario del recurso a la vez.
#include <memory>
std::unique_ptr<Player> player = std::make_unique<Player>("Hero");
player->attack(); // Usar como un puntero regular
// No se puede copiar, pero se puede transferir la propiedad
std::unique_ptr<Player> player2 = std::move(player);
// player ahora es nullptrshared_ptr - propiedad compartida. Múltiples punteros pueden poseer el mismo recurso. La memoria se libera cuando el último propietario es destruido.
std::shared_ptr<Player> p1 = std::make_shared<Player>("Hero");
std::shared_ptr<Player> p2 = p1; // Ambos son dueños del recurso
std::cout << p1.use_count(); // Imprime 2weak_ptr - observador sin propiedad de un shared_ptr. Útil para romper referencias circulares.
std::shared_ptr<Player> shared = std::make_shared<Player>("Hero");
std::weak_ptr<Player> weak = shared;
if (auto locked = weak.lock()) { // Convertir a shared_ptr
locked->attack();
}Usa unique_ptr por defecto para propiedad única. Usa shared_ptr cuando varios objetos necesiten compartir un recurso. Usa weak_ptr para observar sin extender el tiempo de vida.
Prefiere make_unique y make_shared en lugar de new directamente.
Desafío
FácilVamos a construir un sistema para compartir documentos que demuestre los diferentes modelos de propiedad de los punteros inteligentes (smart pointers). Verás cómo unique_ptr impone la propiedad exclusiva, cómo shared_ptr permite múltiples propietarios y cómo weak_ptr puede observar sin afectar el tiempo de vida.
Crearás dos archivos para organizar tu código:
Document.h: Define una claseDocumentque represente un documento compartible. Debe tener:- Un atributo privado
title(string) - Un constructor que reciba un título e imprima
"Document created: <title>" - Un destructor que imprima
"Document deleted: <title>" - Un método
getTitle()que devuelva el título
- Un atributo privado
main.cpp: Demuestra los tres tipos de punteros inteligentes leyendo el título de un documento desde la entrada, y luego:- Crea un
unique_ptra un Document con el título ingresado - Imprime
"Unique owner: <title>" - Transfiere la propiedad a otro
unique_ptrusandostd::move - Imprime
"Ownership transferred" - Crea un
shared_ptra un nuevo Document con el título"SharedDoc" - Crea un segundo
shared_ptrque apunte al mismo documento - Imprime
"Shared owners: <count>"usandouse_count() - Crea un
weak_ptra partir de uno de los punteros compartidos - Usa
lock()para acceder al documento a través del puntero débil e imprime"Weak access: <title>"
- Crea un
Recuerda incluir <memory> para los punteros inteligentes. Usa std::make_unique y std::make_shared para crear tus punteros inteligentes. Los mensajes del destructor te ayudarán a ver cuándo se limpia automáticamente cada documento al final del programa.
Incluye tu archivo de cabecera en main.cpp usando #include "Document.h".
Hoja de referencia
Los punteros inteligentes son clases envolventes que gestionan automáticamente la memoria del montículo (heap). Incluye el encabezado <memory> para usarlos.
unique_ptr
Propiedad exclusiva - solo un puntero puede poseer el recurso a la vez:
std::unique_ptr<Player> player = std::make_unique<Player>("Hero");
player->attack(); // Úsalo como un puntero normal
// Transfiere la propiedad con std::move
std::unique_ptr<Player> player2 = std::move(player);
// player ahora es nullptrshared_ptr
Propiedad compartida - múltiples punteros pueden poseer el mismo recurso. La memoria se libera cuando el último propietario es destruido:
std::shared_ptr<Player> p1 = std::make_shared<Player>("Hero");
std::shared_ptr<Player> p2 = p1; // Ambos poseen el recurso
std::cout << p1.use_count(); // Imprime 2weak_ptr
Observador sin propiedad de un shared_ptr. Útil para romper referencias circulares:
std::shared_ptr<Player> shared = std::make_shared<Player>("Hero");
std::weak_ptr<Player> weak = shared;
if (auto locked = weak.lock()) { // Convertir a shared_ptr
locked->attack();
}Mejores prácticas: Usa unique_ptr por defecto para propiedad única. Usa shared_ptr cuando múltiples objetos necesiten compartir un recurso. Usa weak_ptr para observar sin extender el tiempo de vida. Prefiere make_unique y make_shared sobre el uso directo de new.
Pruébalo tú mismo
#include <iostream>
#include <memory>
#include <string>
#include "Document.h"
using namespace std;
int main() {
string inputTitle;
cin >> inputTitle;
// TODO: Crear un unique_ptr a un Document con inputTitle
// usando std::make_unique
// TODO: Imprimir "Unique owner: <title>"
// TODO: Transferir la propiedad a otro unique_ptr usando std::move
// TODO: Imprimir "Ownership transferred"
// TODO: Crear un shared_ptr a un nuevo Document con el título "SharedDoc"
// usando std::make_shared
// TODO: Crear un segundo shared_ptr que apunte al mismo documento
// TODO: Imprimir "Shared owners: <count>" usando use_count()
// TODO: Crear un weak_ptr a partir de uno de los punteros compartidos
// TODO: Usar lock() para acceder al documento a través de weak_ptr
// y imprimir "Weak access: <title>"
return 0;
}
Esta lección incluye un breve cuestionario. Empieza la lección para responderlo y registrar tu progreso.
Todas las lecciones de Programación Orientada a Objetos
1Fundamentos de OOP
Archivos externosConstrucción y compilación en C++Archivos de cabecera y archivos fuenteNamespaces y alcanceIntroducción a OOP en C++Clases vs ObjetosEl puntero 'this'Métodos (Funciones miembro)Atributos (Miembros de datos)Conceptos básicos de Ctors y DtorsResumen - Calculadora simple4Propiedades de clase
Miembros de instancia vs. estáticosGetters y SettersFunciones miembro constPalabra clave mutableMétodos y variables estáticosFunciones y clases amigasResumen - Gestor de cuentas bancarias7Herencia
Herencia básicaNiveles de acceso en la herenciaOrden de llamada de Ctor y DtorSobrescritura de métodosFunciones virtuales y VTableHerencia múltipleHerencia virtualResumen - Jerarquía de empleados2Gestión de memoria
Memoria Stack vs HeapPunteros y referenciasMemoria dinámica (new/delete)Punteros inteligentes en C++RAII en C++Resumen - Gestor de arrays dinámicos5Encapsulamiento
Especificadores de acceso en C++Especificadores de acceso en profundidadOcultamiento de informaciónStruct vs ClassClases anidadas e internasResumen - Sistema de registros de estudiantes8Polimorfismo
Polimorfismo: Compilación vs. Tiempo de ejecuciónSobrecarga de funcionesFunciones virtuales revisadasFunciones virtuales purasClases abstractasDiseño de interfaces en C++Dynamic Casting y RTTIResumen: Calculadora de figuras11Conceptos avanzados de POO
Composición vs. HerenciaMixins mediante CRTPIdioma PimplBorrado de tiposEnum Classes y tipado fuerteManejo de excepciones en POOJerarquías de excepciones personalizadas14Patrones de diseño - Parte 2
Patrón CommandPatrón AdapterPatrón DecoratorPatrón Template MethodPatrón StatePatrón CompositeRAII como patrón3Constructores y Destructores
Constructor por defectoConstructor parametrizadoConstructor de copiaConstructor de movimientoListas de inicialización del constructorConstructores delegadosAnálisis profundo del destructorRegla de tres / cinco / ceroResumen - Clase String6Sobrecarga de operadores
Introducción a la sobrecarga de operadoresSobrecarga de operadores aritméticosSobrecarga de operadores de comparaciónOperadores de flujo (Stream)Sobrecarga del operador de asignaciónSobrecarga de los operadores [] y ()Operadores de conversión de tiposResumen - Clase Matrix9Plantillas
Plantillas de funcionesPlantillas de clasesEspecialización de plantillasPlantillas variádicasConceptos básicos de SFINAE y Type TraitsResumen - Contenedor genérico