std::unique_ptr în C++

Pointer inteligent care deține singur un obiect și îl șterge automat când iese din domeniu.

Sintaxă
#include <memory>
std::unique_ptr<T> p = std::make_unique<T>(argumente...);
std::unique_ptr<T[]> tablou = std::make_unique<T[]>(n);

Ce face std::unique_ptr

std::unique_ptr înlocuiește perechea new/delete. Ține adresa unui obiect alocat dinamic și îl distruge automat (apelează delete) când pointerul iese din domeniu, chiar dacă între timp a apărut o excepție. Nu mai poți uita să eliberezi memoria.

Este „unic”: un singur unique_ptr deține obiectul la un moment dat. Nu se poate copia, dar se poate muta cu std::move; după mutare, pointerul vechi devine gol (nullptr). Nu costă nimic în plus față de un pointer obișnuit.

Se creează aproape mereu cu std::make_unique. Accesul la obiect se face ca la un pointer: *p și p->membru. Într-un if, pointerul este adevărat dacă deține ceva.

Ce returnează

Un obiect care deține pointerul. Se folosește cu *, ->, get(), reset(), release().

Exemple

Exemplul 1: Obiectul este distrus automat

Destructorul clasei afișează un mesaj, ca să vezi exact când este șters obiectul: la ieșirea din blocul în care a fost creat pointerul.

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

struct Fisier {
    std::string nume;
    explicit Fisier(std::string n) : nume(std::move(n)) { std::println("Deschid {}", nume); }
    ~Fisier() { std::println("Închid {}", nume); }
};

int main() {
    {
        auto f = std::make_unique<Fisier>("note.txt");
        std::println("Lucrez cu {}", f->nume);
    }   // aici f iese din domeniu și obiectul este șters
    std::println("După bloc");
}

Rezultat

Deschid note.txt
Lucrez cu note.txt
Închid note.txt
După bloc

Exemplul 2: Transferul proprietății cu std::move

Un unique_ptr nu se copiază. Cu std::move, obiectul trece la alt pointer, iar cel vechi rămâne gol.

#include <memory>
#include <print>
#include <utility>

int main() {
    std::unique_ptr<int> a = std::make_unique<int>(42);
    std::unique_ptr<int> b = std::move(a);   // auto b = a; nu compilează

    std::println("a este gol: {}", a == nullptr);
    std::println("b = {}", *b);
}

Rezultat

a este gol: true
b = 42

Exemplul 3: Polimorfism: un vector de figuri

Un std::vector<std::unique_ptr<Baza>> ține obiecte de tipuri derivate diferite. La sfârșitul programului toate sunt șterse automat.

#include <memory>
#include <print>
#include <vector>

struct Figura {
    virtual ~Figura() = default;
    virtual double arie() const = 0;
};
struct Patrat : Figura {
    double l;
    explicit Patrat(double latura) : l(latura) {}
    double arie() const override { return l * l; }
};
struct Dreptunghi : Figura {
    double a, b;
    Dreptunghi(double x, double y) : a(x), b(y) {}
    double arie() const override { return a * b; }
};

int main() {
    std::vector<std::unique_ptr<Figura>> figuri;
    figuri.push_back(std::make_unique<Patrat>(3));
    figuri.push_back(std::make_unique<Dreptunghi>(2, 5));

    for (const auto& f : figuri) {
        std::println("Arie: {}", f->arie());
    }
}

Rezultat

Arie: 9
Arie: 10

Greșeli frecvente

  • Încerci să copiezi un unique_ptr (de exemplu îl dai prin valoare unei funcții fără std::move). Eroarea de compilare spune că constructorul de copiere este șters (deleted).
  • Folosești pointerul după std::move. Pointerul vechi este nullptr, iar *p pe el este comportament nedefinit.
  • Clasa de bază nu are destructor virtual. Ștergerea unui obiect derivat printr-un unique_ptr<Baza> nu mai apelează destructorul derivat.

Comenzi înrudite

  • std::make_unique: Creează un obiect pe heap și returnează un std::unique_ptr care îl deține.
  • std::unique_ptr::reset: reset șterge obiectul deținut (și poate prelua altul); release renunță la proprietate fără să șteargă.
  • std::unique_ptr::get: Returnează pointerul brut către obiectul deținut, fără să renunțe la proprietate.
  • std::shared_ptr: Pointer inteligent cu proprietate comună: obiectul este șters când ultimul shared_ptr dispare.
  • std::move: Mută elementele unui interval la o destinație, fără să le copieze (versiunea din <algorithm>).
  • pointeri (*, &): Un pointer este o variabilă care ține adresa altei variabile; prin el poți citi și modifica valoarea de la acea adresă.