shared_ptr, weak_ptr y Grafos de Propiedad
Comprende la propiedad compartida con conteo de referencias con shared_ptr, rompe ciclos con weak_ptr, aprende sobre bloques de control y aliasing, y sabe cuando shared_ptr es la eleccion incorrecta.
shared_ptr: Propiedad Compartida
std::shared_ptr habilita la propiedad compartida — multiples instancias de shared_ptr pueden poseer el mismo objeto. El objeto se destruye cuando el ultimo shared_ptr propietario es destruido o reseteado.
Internamente, shared_ptr mantiene un bloque de control que contiene:
- Un conteo de referencias fuertes (numero de propietarios shared_ptr)
- Un conteo de referencias debiles (numero de observadores weak_ptr)
- El deleter y el asignador
shared_ptr es mas grande que un puntero crudo (tipicamente 2 punteros: uno al objeto, uno al bloque de control) y el conteo de referencias tiene sobrecarga atomica en cada copia/destruccion. Usalo solo cuando la propiedad sea genuinamente compartida.
make_shared y Conteo de Referencias
std::make_shared asigna el objeto y el bloque de control en una unica asignacion — mejor localidad de cache, menos fragmentacion de memoria y seguridad ante excepciones.
#include <iostream>
#include <memory>
#include <string>
struct Logger {
std::string name;
Logger(std::string n) : name(std::move(n)) {
std::cout << name << " created\n";
}
~Logger() { std::cout << name << " destroyed\n"; }
void log(const std::string& msg) {
std::cout << "[" << name << "] " << msg << '\n';
}
};
int main() {
auto logger = std::make_shared<Logger>("AppLogger");
std::cout << "use_count: " << logger.use_count() << '\n'; // 1
{
auto copy = logger; // shared ownership
std::cout << "use_count: " << logger.use_count() << '\n'; // 2
copy->log("inside scope");
} // copy destroyed, count drops to 1
std::cout << "use_count: " << logger.use_count() << '\n'; // 1
logger->log("still alive");
} // logger destroyed — count drops to 0, Logger deletedweak_ptr: Rompiendo Ciclos
std::weak_ptr es un observador sin propiedad de un objeto gestionado por shared_ptr. No incrementa el conteo de referencias fuertes — asi que no mantiene vivo el objeto.
Caso de uso principal: romper ciclos de propiedad. Si A posee un shared_ptr a B y B posee un shared_ptr a A, ninguno sera destruido jamas (conteo de referencias circular). Haz que uno de los punteros sea un weak_ptr para romper el ciclo.
Para acceder al objeto, llama a weak_ptr::lock(), que retorna un shared_ptr. Si el objeto ya fue destruido, lock() retorna un shared_ptr nulo. Siempre verifica el resultado de lock().
Ciclo de weak_ptr en Accion
Este ejemplo muestra como weak_ptr rompe un ciclo de propiedad que de otro modo evitaria la destruccion.
El Constructor Aliasing
El constructor aliasing de shared_ptr te permite crear un shared_ptr que comparte propiedad con un shared_ptr existente pero apunta a un subobjeto diferente. El objeto original permanece vivo mientras cualquier shared_ptr aliased exista.
struct Pair { int first; int second; };
auto p = std::make_shared<Pair>(1, 2);
std::shared_ptr<int> q(p, &p->first); // shares ownership, points to firstEsto es util para pasar un miembro de un objeto gestionado por shared_ptr a codigo que espera un shared_ptr. El conteo de referencias del objeto padre se usa, asi que el padre vive mientras alguien tenga un puntero aliased a cualquier parte de el.
1. Ciclos de propiedad — dos shared_ptr apuntandose mutuamente nunca liberan memoria. Usa weak_ptr para romper ciclos.
2. Construir desde this — shared_ptr dentro de un metodo crea un bloque de control separado, causando doble delete. Usa std::enable_shared_from_this en su lugar.
3. Sobre-compartir — usar shared_ptr en todas partes "por si acaso" es un anti-patron. Paga la sobrecarga atomica solo cuando la propiedad compartida es genuinamente necesaria. Por defecto usa unique_ptr.
4. No es seguro para hilos — el conteo de referencias es atomico, pero el objeto apuntado no. Multiples hilos leyendo/escribiendo el mismo shared_ptr (no el objeto, el propio puntero) requiere sincronizacion o std::atomic (C++20).
enable_shared_from_this
Cuando un objeto gestionado por shared_ptr necesita obtener un shared_ptr a si mismo (por ejemplo, para registrarse en un callback), debe heredar de std::enable_shared_from_this y llamar a shared_from_this().
#include <memory>
#include <iostream>
struct Widget {
int data[4];
};
int main() {
// Two allocations: one for Widget, one for control block
std::shared_ptr<Widget> p1(new Widget{});
// Single allocation: Widget + control block together
auto p2 = std::make_shared<Widget>();
// Sizes (typical 64-bit platform)
std::cout << "sizeof(Widget*): " << sizeof(Widget*) << '\n'; // 8
std::cout << "sizeof(unique_ptr<Widget>): " << sizeof(std::unique_ptr<Widget>) << '\n'; // 8
std::cout << "sizeof(shared_ptr<Widget>): " << sizeof(std::shared_ptr<Widget>) << '\n'; // 16
// shared_ptr is 2 pointers: one to object, one to control block
// Control block contains: strong count, weak count, deleter, allocator
std::cout << "p2 use_count: " << p2.use_count() << '\n'; // 1
std::weak_ptr<Widget> w = p2;
std::cout << "p2 use_count: " << p2.use_count() << '\n'; // still 1
// weak_ptr does NOT increase strong count
}shared_ptrproporciona propiedad compartida con conteo de referencias — el objeto se destruye cuando el ultimo propietario mueremake_sharedasigna el objeto y el bloque de control juntos — una asignacion en vez de dosweak_ptrrompe ciclos de propiedad — observa sin mantener vivo el objeto- Siempre verifica
weak_ptr::lock()— el objeto puede haber sido destruido - Usa
enable_shared_from_thiscuando un objeto necesita unshared_ptra si mismo - Por defecto usa
unique_ptr—shared_ptrsolo cuando la propiedad compartida es genuinamente necesaria
Quiz — Pon a prueba tus conocimientos
(15 XP)1. Que sucede cuando dos objetos mantienen `shared_ptr` el uno al otro?
2. Cual es la ventaja de `make_shared` sobre `shared_ptr<T>(new T(...))`?
3. Como accedes de forma segura a un objeto a traves de un `weak_ptr`?