Ghid de Gestiune a Memoriei în C++ Modern: RAII, Move Semantics și Pointeri Inteligenți (unique_ptr, shared_ptr) în C++

Pointeri inteligenți și memorie Standard C++ Modern

Ghid complet de gestiune sigură a memoriei în C++: eliminarea scurgerilor de memorie cu std::make_unique și std::make_shared, evitarea referințelor circulare cu std::weak_ptr, regula celor 5 (Rule of 5) și semantica de mutare.

Sintaxă
auto ptr = std::make_unique<Resursa>(); | auto shared = std::make_shared<Resursa>();

În ghidul oficial de bune practici C++ Core Guidelines (coordonat de Bjarne Stroustrup și Herb Sutter), prima și cea mai importantă regulă este simplă: nu utilizați niciodată manual operatorii new și delete în codul de aplicație. Alocarea manuală este principala cauză a scurgerilor de memorie (Memory Leaks), a accesărilor de memorie eliberată (Use-After-Free) și a eliberărilor duble (Double Free). C++-ul modern rezolvă definitiv aceste probleme prin pointeri inteligenți bazați pe principiul RAII (Resource Acquisition Is Initialization).

1. std::unique_ptr: Deținere Exclusivă cu Cost Zero (Zero-Overhead)

std::unique_ptr reprezintă proprietatea unică și exclusivă asupra unui obiect de pe heap. Nu poate fi copiat (copy constructorul este șters), dar poate fi mutat (moved) prin std::move. Dimensiunea sa în memorie este exact egală cu dimensiunea unui pointer brut (8 octeți pe arhitecturi 64-bit), oferind viteză nativă absolută:

Utilizarea corectă a lui std::unique_ptr
#include <memory>
#include <print>

struct ConexiuneBazaDate {
    ConexiuneBazaDate() { std::println("Conexiune deschisa."); }
    ~ConexiuneBazaDate() { std::println("Conexiune inchisa automat!"); }
    void executaInterogare() { std::println("Interogare SQL rulata."); }
};

void proceseaza() {
    // 1. Creare sigură dintr-un singur apel cu std::make_unique:
    auto db = std::make_unique<ConexiuneBazaDate>();
    db->executaInterogare();

    // 2. Transfer de proprietate către o altă funcție sau variabilă:
    auto altDb = std::move(db); // 'db' devine nullptr, 'altDb' deține resursa!

    // LA IEȘIREA DIN BLOC: Destructorul este apelat automat, memoria este eliberată!
}

2. std::shared_ptr și std::weak_ptr: Deținere Partajată

Dacă o resursă trebuie să fie partajată între multiple subsisteme și nu este clar cine va termina ultimul lucrul cu ea, se utilizează std::shared_ptr, care menține un contor atomic de referințe (Reference Count):

  • Fiecare copiere a pointerului incrementează contorul.
  • Fiecare distrugere a unei instanțe decrementează contorul.
  • Când contorul ajunge la zero, obiectul de pe heap este distrus instantaneu.
Capcana Referințelor Circulare: Dacă Obiectul A deține un shared_ptr către Obiectul B, iar Obiectul B deține un shared_ptr către Obiectul A, contorul nu va ajunge niciodată la zero, generând o scurgere permanentă de memorie! Soluția este transformarea uneia dintre referințe în std::weak_ptr (o referință slabă care observă obiectul fără a-i incrementa contorul de proprietate).

3. Regula celor Cinci (Rule of Five)

Dacă o clasă gestionează manual o resursă brută de sistem de nivel scăzut, trebuie să implementați explicit (sau să ștergeți cu = delete) toate cele 5 funcții speciale ale ciclului de viață:

class ResursaCustom {
public:
    // 1. Destructor
    ~ResursaCustom() noexcept;

    // 2. Constructor de copiere (Copy Constructor)
    ResursaCustom(const ResursaCustom& other);

    // 3. Operator de atribuire prin copiere (Copy Assignment)
    ResursaCustom& operator=(const ResursaCustom& other);

    // 4. Constructor de mutare (Move Constructor - noexcept obligatoriu!)
    ResursaCustom(ResursaCustom&& other) noexcept;

    // 5. Operator de atribuire prin mutare (Move Assignment)
    ResursaCustom& operator=(ResursaCustom&& other) noexcept;
};