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_ptra partir de um ponteiro. - Quando se cria um
std::shared_ptrcom um eliminador ou alocador personalizado. - Ao usar
std::make_sharedoustd::allocate_shared.
Este bloco de controlo contém pelo menos dois contadores:
- Contador de referências fortes (strong count): Aumenta ao criar ou copiar um
std::shared_ptr. Diminui ao destruir umstd::shared_ptr. Quando este contador chega a zero, o objeto gerido é libertado. - Contador de referências fracas (weak count): Aumenta ao criar um
std::weak_ptra partir de umstd::shared_ptr. Diminui ao destruir umstd::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