Sari la conținut

shared_ptr, weak_ptr și grafice de proprietate

Înțelegeți proprietățile partajate cu referințe numărate cu shared_ptr, întrerupeți ciclurile cu weak_ptr, aflați despre blocurile de control și aliasing și aflați când shared_ptr este alegerea greșită.

shared_ptr: Proprietate comună

std::shared_ptrpermite proprietatea partajată — mai multe instanțeshared_ptrpot deține același obiect. Obiectul este distrus atunci când ultimul care dețineshared_ptreste distrus sau resetat.

Intern,shared_ptrmenține un bloc de control care conține:
- Un număr puternic de referințe (număr de proprietarishared_ptr)
- Un număr slab de referință (număr de observatoriweak_ptr)
- Deleter și alocator

shared_ptreste mai mare decât un pointer brut (de obicei, 2 pointere: unul către obiect, unul către blocul de control) și numărarea referințelor are supraveghere atomică la fiecare copie/distrugere. Folosiți-l numai atunci când proprietatea este partajată cu adevărat.

make_shared și Reference Counting

std::make_shared(args...)alocă obiectul ȘI blocul de control într-o alocare unică (în loc de două). Acest lucru este mai prietenos cu memoria cache și evită problema de siguranță excepțională anewbrut.

shared_ptr_basics.cpp
#include <iostream>
#include <memory>
#include <string>

struct Logger {
    std::string name;
    Logger(std::string n) : name(std::move(n)) {
        std::cout << name << " created\n";
    }
    ~Logger() { std::cout << name << " destroyed\n"; }
    void log(const std::string& msg) {
        std::cout << "[" << name << "] " << msg << '\n';
    }
};

int main() {
    auto logger = std::make_shared<Logger>("AppLogger");
    std::cout << "use_count: " << logger.use_count() << '\n';  // 1

    {
        auto copy = logger;  // shared ownership
        std::cout << "use_count: " << logger.use_count() << '\n';  // 2
        copy->log("inside scope");
    }  // copy destroyed, count drops to 1

    std::cout << "use_count: " << logger.use_count() << '\n';  // 1
    logger->log("still alive");
}  // logger destroyed — count drops to 0, Logger deleted

weak_ptr: Cicluri de întrerupere

std::weak_ptrobservă un obiect gestionat deshared_ptrfără a prelungi durata de viață. Pentru a accesa obiectul, apelațilock(), care returnează unshared_ptr- saunullptrdacă obiectul a fost deja distrus.weak_ptreste esențial pentru spargerea referințelor circulare care altfel s-ar scurge.

weak_ptr_example.cpp
#include <iostream>
#include <memory>
#include <string>

struct Employee;
struct Team {
    std::string name;
    std::vector<std::shared_ptr<Employee>> members;
    Team(std::string n) : name(std::move(n)) {}
    ~Team() { std::cout << "Team " << name << " destroyed\n"; }
};

struct Employee {
    std::string name;
    std::weak_ptr<Team> team;  // weak — does NOT own the team
    Employee(std::string n) : name(std::move(n)) {}
    ~Employee() { std::cout << "Employee " << name << " destroyed\n"; }

    void print_team() {
        if (auto t = team.lock()) {  // try to get shared_ptr
            std::cout << name << " belongs to " << t->name << '\n';
        } else {
            std::cout << name << " has no team\n";
        }
    }
};

int main() {
    auto team = std::make_shared<Team>("Engineering");
    auto alice = std::make_shared<Employee>("Alice");
    auto bob = std::make_shared<Employee>("Bob");

    team->members.push_back(alice);
    team->members.push_back(bob);
    alice->team = team;  // weak_ptr — no circular ownership
    bob->team = team;

    alice->print_team();  // "Alice belongs to Engineering"

    team.reset();  // destroy team
    alice->print_team();  // "Alice has no team" — weak_ptr expired
}
Capcană

Dacă două obiecte ținshared_ptrunul pe celălalt, niciunul nu poate atinge niciodată numărul de referință zero - se mențin unul pe celălalt în viață pentru totdeauna. Aceasta este o scăpare de memorie, chiar dacă utilizați pointeri inteligente.

Indicatorul ``cpp
struct Node {
std::shared_ptr next; // if next points back, LEAK!
};
auto a = std::make_shared();
auto b = std::make_shared();
a->next = b;
b->next = a; // circular — neither a nor b will ever be destroyed
``

The fix: make one direction a weak_ptr. In tree structures, children hold weak_ptr to parents (or raw observer pointers). In doubly-linked lists, use weak_ptr for the prev.

Constructorul Aliasing și enable_shared_from_this

Constructorul de alias creează unshared_ptrcare împarte proprietatea cu unshared_ptrexistent, dar indică un obiect diferit (de obicei un membru). Obiectul cu alias rămâne în viață atâta timp cât există orice aliasshared_ptr:

struct Pair { int first; int second; };
auto p = std::make_shared<Pair>(Pair{1, 2});
std::shared_ptr<int> sp_first(p, &p->first);  // shares p's control block
// p can go out of scope — Pair stays alive because sp_first still references it

enable_shared_from_this is a CRTP base class that allows an object managed by shared_ptr to obtain a shared_ptr to itself via shared_from_this(). Without it, constructing a new shared_ptr from this creează un bloc de control dublu liber.

Bună practică

shared_ptreste suprafolosit. Atinge-l numai atunci când calitatea de proprietar este împărțită cu adevărat de-a lungul vieților independente. NU utilizațishared_ptrca un înlocuitor leneș pentru a vă gândi la proprietate.

Evitațishared_ptrcând:
- Există un singur proprietar - utilizați în schimbunique_ptr(zero suprasarcină).
- Obiectul locuiește pe stivă — doar folosește obiectul direct.
- Trebuie doar să transmiteți o referință - trecețiT&sauT*.
- Il utilizați „pentru că este sigur” —shared_ptrare costuri de execuție (număr de referințe atomice, alocare suplimentară) și ascunde semantica proprietății.
- În căile critice pentru performanță — operațiunile de referință atomică sunt surprinzător de costisitoare.

Scenarii legitime de proprietate partajată: cache-urile (căutări multiple returnează același obiect), sisteme pub/sub, obiecte de configurare partajată și structuri de date grafice cu noduri partajate cu adevărat.

Bloc de control intern

Înțelegerea blocului de control ajută la explicarea costurilorshared_ptr.make_sharedcombină obiectul și blocul de control într-o singură alocare. Construirea dinnewbrut necesită două alocări separate.

control_block.cpp
#include <memory>
#include <iostream>

struct Widget {
    int data[4];
};

int main() {
    // Two allocations: one for Widget, one for control block
    std::shared_ptr<Widget> p1(new Widget{});

    // Single allocation: Widget + control block together
    auto p2 = std::make_shared<Widget>();

    // Sizes (typical 64-bit platform)
    std::cout << "sizeof(Widget*):          " << sizeof(Widget*) << '\n';   // 8
    std::cout << "sizeof(unique_ptr<Widget>): " << sizeof(std::unique_ptr<Widget>) << '\n';  // 8
    std::cout << "sizeof(shared_ptr<Widget>): " << sizeof(std::shared_ptr<Widget>) << '\n';  // 16

    // shared_ptr is 2 pointers: one to object, one to control block
    // Control block contains: strong count, weak count, deleter, allocator

    std::cout << "p2 use_count: " << p2.use_count() << '\n';  // 1
    std::weak_ptr<Widget> w = p2;
    std::cout << "p2 use_count: " << p2.use_count() << '\n';  // still 1
    // weak_ptr does NOT increase strong count
}
Idei cheie
  • shared_ptrfolosește contorizarea referințelor - obiectul moare când ultimul proprietar este plecat
  • make_sharedalocă obiect + bloc de control într-o singură alocare (prefer-o întotdeauna)
  • weak_ptrobservă fără a deține — utilizațilock()pentru a accesa în siguranță, rupe referințe circulare
  • shared_ptrare o lățime de 2 pointeri și are supraîncărcarea de refcount atomic - nu este gratuit
  • Implicit launique_ptr; utilizațishared_ptrnumai atunci când proprietatea este partajată cu adevărat
  • Nu construiți niciodată douăshared_ptrdin același indicator brut - utilizațienable_shared_from_thispentru auto-referințe

Test — Verifică-ți cunoștințele

(15 XP)

1. Ce se întâmplă când două obiecte țin`shared_ptr`unul pe celălalt?

2. De ce este`std::make_shared`preferat față de`shared_ptr<T>(new T(...))`?

3. How do you safely access an object through a`weak_ptr`?