Middle
¿Cómo funciona el contador de referencias en std::shared_ptr?
sobes.tech AI
Respuesta de la IA
El contador de referencias en std::shared_ptr se almacena en un objeto separado: el bloque de control (control block).
El bloque de control se crea:
- Cuando se crea por primera vez un
std::shared_ptra partir de un puntero. - Cuando se crea un
std::shared_ptrcon un eliminador o un allocador personalizado. - Al usar
std::make_sharedostd::allocate_shared.
Este bloque de control contiene al menos dos contadores:
- Contador de referencias fuertes (strong count): Se incrementa al crear o copiar un
std::shared_ptr. Se decrementa al destruir unstd::shared_ptr. Cuando este contador llega a cero, se libera el objeto gestionado. - Contador de referencias débiles (weak count): Se incrementa al crear un
std::weak_ptra partir de unstd::shared_ptr. Se decrementa al destruir unstd::weak_ptr. El bloque de control se libera cuando ambos contadores — fuertes y débiles — llegan a cero.
El uso de std::make_shared es preferible a la creación directa con new, ya que puede asignar el objeto y el bloque de control en un solo bloque de memoria, lo que mejora el rendimiento y reduce la fragmentación.
Los contadores son atómicos, lo que hace que std::shared_ptr sea seguro para su uso en escenarios multihilo, aunque el acceso al objeto gestionado no está sincronizado por defecto.
Ejemplo:
#include <iostream>
#include <memory>
struct MyObject {
int data;
MyObject(int d) : data(d) {
std::cout << "MyObject(" << data << ") creado\n";
}
~MyObject() {
std::cout << "MyObject(" << data << ") destruido\n";
}
};
int main() {
std::shared_ptr<MyObject> ptr1 = std::make_shared<MyObject>(10);
// contador fuerte: 1, contador débil: 0
{
std::shared_ptr<MyObject> ptr2 = ptr1;
// contador fuerte: 2, contador débil: 0
std::weak_ptr<MyObject> weak_ptr = ptr1;
// contador fuerte: 2, contador débil: 1
if (auto locked_ptr = weak_ptr.lock()) {
// contador fuerte: 3, contador débil: 1 (temporalmente dentro de lock)
std::cout << "Datos del objeto: " << locked_ptr->data << std::endl;
}
// contador fuerte: 2, contador débil: 1
} // ptr2 sale del ámbito
// contador fuerte: 1, contador débil: 1
// El objeto aún no ha sido destruido, ya que el contador fuerte == 1
{
std::weak_ptr<MyObject> weak_ptr2 = ptr1;
// contador fuerte: 1, contador débil: 2
} // weak_ptr2 sale del ámbito
// contador fuerte: 1, contador débil: 1
} // ptr1 sale del ámbito
// contador fuerte: 0, contador débil: 1
// El objeto se destruye, ya que el contador fuerte llegó a 0
// El bloque de control permanece hasta que el contador débil llegue a 0