Sobes.tech
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_ptr a partir de un puntero.
  • Cuando se crea un std::shared_ptr con un eliminador o un allocador personalizado.
  • Al usar std::make_shared o std::allocate_shared.

Este bloque de control contiene al menos dos contadores:

  1. Contador de referencias fuertes (strong count): Se incrementa al crear o copiar un std::shared_ptr. Se decrementa al destruir un std::shared_ptr. Cuando este contador llega a cero, se libera el objeto gestionado.
  2. Contador de referencias débiles (weak count): Se incrementa al crear un std::weak_ptr a partir de un std::shared_ptr. Se decrementa al destruir un std::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