Sobes.tech
Middle

Como funciona o contador de referências em std::shared_ptr?

sobes.tech IA

Resposta da IA

O contador de referências em std::shared_ptr é armazenado num objeto separado: o bloco de controlo (control block).

O bloco de controlo é criado:

  • Quando se cria pela primeira vez um std::shared_ptr a partir de um ponteiro.
  • Quando se cria um std::shared_ptr com um eliminador ou alocador personalizado.
  • Ao usar std::make_shared ou std::allocate_shared.

Este bloco de controlo contém pelo menos dois contadores:

  1. Contador de referências fortes (strong count): Aumenta ao criar ou copiar um std::shared_ptr. Diminui ao destruir um std::shared_ptr. Quando este contador chega a zero, o objeto gerido é libertado.
  2. Contador de referências fracas (weak count): Aumenta ao criar um std::weak_ptr a partir de um std::shared_ptr. Diminui ao destruir um std::weak_ptr. O bloco de controlo é libertado quando ambos os contadores — fortes e fracos — chegam a zero.

O uso de std::make_shared é preferível à criação direta com new, pois pode alocar o objeto e o bloco de controlo numa única área de memória, o que melhora o desempenho e reduz a fragmentação.

Os contadores são atómicos, o que torna o std::shared_ptr seguro para uso em cenários multithread, embora o acesso ao objeto gerido não seja sincronizado por padrão.

Exemplo:

#include <iostream>
#include <memory>

struct MyObject {
    int data;
    MyObject(int d) : data(d) {
        std::cout << "MyObject(" << data << ") criado\n";
    }
    ~MyObject() {
        std::cout << "MyObject(" << data << ") destruído\n";
    }
};

int main() {
    std::shared_ptr<MyObject> ptr1 = std::make_shared<MyObject>(10);
    // contador forte: 1, contador fraco: 0

    {
        std::shared_ptr<MyObject> ptr2 = ptr1;
        // contador forte: 2, contador fraco: 0

        std::weak_ptr<MyObject> weak_ptr = ptr1;
        // contador forte: 2, contador fraco: 1

        if (auto locked_ptr = weak_ptr.lock()) {
            // contador forte: 3, contador fraco: 1 (temporariamente dentro de lock)
            std::cout << "Dados do objeto: " << locked_ptr->data << std::endl;
        }
        // contador forte: 2, contador fraco: 1
    } // ptr2 sai do escopo
    // contador forte: 1, contador fraco: 1

    // O objeto ainda não foi destruído, pois o contador forte == 1

    {
        std::weak_ptr<MyObject> weak_ptr2 = ptr1;
        // contador forte: 1, contador fraco: 2
    } // weak_ptr2 sai do escopo
    // contador forte: 1, contador fraco: 1

} // ptr1 sai do escopo
// contador forte: 0, contador fraco: 1
// O objeto é destruído, pois o contador forte chegou a 0
// O bloco de controlo permanece até que o contador fraco chegue a 0