std::weak_ptr în C++

Referință slabă la un obiect deținut de shared_ptr: nu îl ține în viață; lock() îl folosește în siguranță.

Sintaxă
std::weak_ptr<T> w = un_shared_ptr;
std::shared_ptr<T> lock() const noexcept;
bool expired() const noexcept;

Ce face std::weak_ptr

std::weak_ptr „observă” un obiect deținut de unul sau mai mulți std::shared_ptr, fără să crească contorul. Obiectul poate fi șters oricând de proprietarii lui.

Ca să folosești obiectul, apelezi w.lock(): dacă obiectul încă există, primești un shared_ptr valid (care îl ține în viață cât timp îl folosești); dacă nu, primești un shared_ptr gol. w.expired() spune doar dacă obiectul a dispărut.

Principala utilizare: ruperea ciclurilor dintre shared_ptr (copilul ține un weak_ptr spre părinte) și cache-uri care nu trebuie să țină obiectele în viață.

Ce returnează

lock() returnează un std::shared_ptr<T>, gol dacă obiectul a fost șters.

Exemple

Exemplul 1: lock() înainte și după ștergere

#include <memory>
#include <print>
#include <string>

void foloseste(const std::weak_ptr<std::string>& w) {
    if (auto p = w.lock()) {
        std::println("Obiectul există: {}", *p);
    } else {
        std::println("Obiectul a fost șters");
    }
}

int main() {
    auto utilizator = std::make_shared<std::string>("Ana");
    std::weak_ptr<std::string> w = utilizator;
    std::println("proprietari: {}", utilizator.use_count());   // weak_ptr nu contează

    foloseste(w);
    utilizator.reset();
    foloseste(w);
    std::println("expired: {}", w.expired());
}

Rezultat

proprietari: 1
Obiectul există: Ana
Obiectul a fost șters
expired: true

Exemplul 2: Ruperea unui ciclu părinte–copil

Părintele deține copilul prin shared_ptr, copilul vede părintele prin weak_ptr. Ambele obiecte sunt șterse la final.

#include <memory>
#include <print>
#include <string>

struct Parinte;

struct Copil {
    std::weak_ptr<Parinte> parinte;
    ~Copil() { std::println("Copil șters"); }
};

struct Parinte {
    std::string nume = "Maria";
    std::shared_ptr<Copil> copil;
    ~Parinte() { std::println("Părinte șters"); }
};

int main() {
    auto p = std::make_shared<Parinte>();
    p->copil = std::make_shared<Copil>();
    p->copil->parinte = p;

    if (auto par = p->copil->parinte.lock()) {
        std::println("Părintele copilului: {}", par->nume);
    }
}

Rezultat

Părintele copilului: Maria
Părinte șters
Copil șters

Greșeli frecvente

  • Verifici !w.expired() și apoi folosești obiectul. Între cele două momente obiectul poate dispărea (mai ales cu fire de execuție). Folosește mereu lock() și verifică rezultatul.

Comenzi înrudite

  • std::shared_ptr: Pointer inteligent cu proprietate comună: obiectul este șters când ultimul shared_ptr dispare.
  • std::make_shared: Creează un obiect și un std::shared_ptr care îl deține, cu o singură alocare de memorie.
  • std::enable_shared_from_this: Clasă de bază care permite unui obiect deținut de shared_ptr să obțină un shared_ptr către el însuși.