Sobes.tech
Middle

Come funziona il contatore di riferimenti in std::shared_ptr?

sobes.tech AI

Risposta dell'AI

Il contatore di riferimenti in std::shared_ptr viene memorizzato in un oggetto separato: nel blocco di controllo (control block).

Il blocco di controllo viene creato:

  • Quando si crea per la prima volta uno std::shared_ptr da un puntatore.
  • Quando si crea uno std::shared_ptr con un eliminatore o allocatore personalizzato.
  • Quando si utilizza std::make_shared o std::allocate_shared.

Questo blocco di controllo contiene almeno due contatori:

  1. Contatore di riferimenti forti (strong count): Aumenta alla creazione o copia di uno std::shared_ptr. Diminuisce alla distruzione di uno std::shared_ptr. Quando questo contatore raggiunge zero, l'oggetto gestito viene liberato.
  2. Contatore di riferimenti deboli (weak count): Aumenta alla creazione di uno std::weak_ptr da uno std::shared_ptr. Diminuisce alla distruzione di uno std::weak_ptr. Il blocco di controllo viene liberato quando entrambi i contatori — forti e deboli — raggiungono zero.

L'uso di std::make_shared è preferibile rispetto alla creazione diretta con new, poiché può allocare l'oggetto e il blocco di controllo in un'unica area di memoria, migliorando le prestazioni e riducendo la frammentazione.

I contatori sono atomici, il che rende std::shared_ptr sicuro per l'uso in scenari multithread, anche se l'accesso all'oggetto gestito non è sincronizzato di default.

Esempio:

#include <iostream>
#include <memory>

struct MyObject {
    int data;
    MyObject(int d) : data(d) {
        std::cout << "MyObject(" << data << ") creato\n";
    }
    ~MyObject() {
        std::cout << "MyObject(" << data << ") distrutto\n";
    }
};

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

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

        std::weak_ptr<MyObject> weak_ptr = ptr1;
        // contatore forte: 2, contatore debole: 1

        if (auto locked_ptr = weak_ptr.lock()) {
            // contatore forte: 3, contatore debole: 1 (temporaneamente in lock)
            std::cout << "Dati oggetto: " << locked_ptr->data << std::endl;
        }
        // contatore forte: 2, contatore debole: 1
    } // ptr2 esce dal scope
    // contatore forte: 1, contatore debole: 1

    // L'oggetto non è ancora stato distrutto, poiché il contatore forte == 1

    {
        std::weak_ptr<MyObject> weak_ptr2 = ptr1;
        // contatore forte: 1, contatore debole: 2
    } // weak_ptr2 esce dal scope
    // contatore forte: 1, contatore debole: 1

} // ptr1 esce dal scope
// contatore forte: 0, contatore debole: 1
// L'oggetto viene distrutto, poiché il contatore forte è diventato 0
// Il blocco di controllo rimane finché il contatore debole non diventa 0