Sobes.tech
Middle

Jak działa licznik referencji w std::shared_ptr?

sobes.tech AI

Odpowiedź od AI

Licznik referencji w std::shared_ptr jest przechowywany w osobnym obiekcie: w bloku kontrolnym (control block).

Blok kontrolny jest tworzony:

  • Przy pierwszym utworzeniu std::shared_ptr z wskaźnika.
  • Przy tworzeniu std::shared_ptr z własnym deleterem lub alokatorami.
  • Przy użyciu std::make_shared lub std::allocate_shared.

Ten blok kontrolny zawiera co najmniej dwa liczniki:

  1. Licznik silnych odniesień (strong count): Zwiększa się przy tworzeniu lub kopiowaniu std::shared_ptr. Zmniejsza się przy niszczeniu std::shared_ptr. Gdy ten licznik osiągnie zero, zarządzany obiekt jest zwalniany.
  2. Licznik słabych odniesień (weak count): Zwiększa się przy tworzeniu std::weak_ptr z std::shared_ptr. Zmniejsza się przy niszczeniu std::weak_ptr. Blok kontrolny jest zwalniany, gdy oba liczniki — silne i słabe — osiągną zero.

Użycie std::make_shared jest preferowane nad bezpośrednim tworzeniem z new, ponieważ może alokować obiekt i blok kontrolny w jednym bloku pamięci, co poprawia wydajność i zmniejsza fragmentację.

Liczniki są atomowe, co czyni std::shared_ptr bezpiecznym do użycia w scenariuszach wielowątkowych, choć dostęp do zarządzanego obiektu nie jest domyślnie synchronizowany.

Przykład:

#include <iostream>
#include <memory>

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

int main() {
    std::shared_ptr<MyObject> ptr1 = std::make_shared<MyObject>(10);
    // licznik silny: 1, licznik słaby: 0

    {
        std::shared_ptr<MyObject> ptr2 = ptr1;
        // licznik silny: 2, licznik słaby: 0

        std::weak_ptr<MyObject> weak_ptr = ptr1;
        // licznik silny: 2, licznik słaby: 1

        if (auto locked_ptr = weak_ptr.lock()) {
            // licznik silny: 3, licznik słaby: 1 (tymczasowo w lock)
            std::cout << "Dane obiektu: " << locked_ptr->data << std::endl;
        }
        // licznik silny: 2, licznik słaby: 1
    } // ptr2 wychodzi z zakresu
    // licznik silny: 1, licznik słaby: 1

    // Obiekt jeszcze nie został zniszczony, ponieważ licznik silny == 1

    {
        std::weak_ptr<MyObject> weak_ptr2 = ptr1;
        // licznik silny: 1, licznik słaby: 2
    } // weak_ptr2 wychodzi z zakresu
    // licznik silny: 1, licznik słaby: 1

} // ptr1 wychodzi z zakresu
// licznik silny: 0, licznik słaby: 1
// Obiekt jest niszczony, ponieważ licznik silny osiągnął 0
// Blok kontrolny pozostaje do momentu, aż licznik słaby osiągnie 0