referințe (&, &&) în C++

Memorie, pointeri și referințe C++98 (referințe rvalue &&: C++11)

O referință este un alt nume pentru o variabilă existentă; tot ce faci cu referința se face de fapt cu variabila originală.

Sintaxă
int& ref = x;            // alt nume pentru x
void f(int& x);          // modifică argumentul
void g(const T& x);      // citește fără copie
void h(T&& x);           // primește temporare (mutare), C++11

Ce face referințe (&, &&)

int& r = x; face din r un alt nume pentru x. Nu se copiază nimic: r = 10; schimbă chiar x. O referință trebuie legată de o variabilă la declarare și nu mai poate fi mutată pe alta. Nu există „referință nulă”.

Cea mai importantă folosire este la parametri. void f(int& x) poate modifica variabila celui care apelează. void f(const std::string& s) primește textul fără să-l copieze, dar nu îl poate modifica: este forma standard pentru obiecte mari pe care doar le citești.

Din C++11 există și referințele rvalue (T&&), care se leagă de obiecte temporare sau marcate cu std::move. Prin ele se implementează mutarea: în loc să copiezi un vector mare, îi „furi” conținutul, pentru că originalul oricum nu mai este folosit.

Exemple

Exemplul 1: Referința ca alt nume

#include <print>

int main() {
    int puncte = 50;
    int& scor = puncte;  // scor este alt nume pentru puncte
    scor += 25;
    std::println("puncte = {}, scor = {}", puncte, scor);
    std::println("Aceeași variabilă: {}", &scor == &puncte);
}

Rezultat

puncte = 75, scor = 75
Aceeași variabilă: true

Exemplul 2: Parametri prin referință

schimba primește referințe, deci modifică variabilele din main. numara_litere primește const&: nu copiază textul și nu îl poate modifica. Prin valoare, schimba nu ar avea niciun efect în afară.

#include <print>
#include <string>

void schimba(int& a, int& b) {
    int temp = a;
    a = b;
    b = temp;
}

int numara_litere(const std::string& text, char litera) {
    int n = 0;
    for (char c : text) {
        if (c == litera) ++n;
    }
    return n;
}

int main() {
    int x = 1, y = 2;
    schimba(x, y);
    std::println("x = {}, y = {}", x, y);
    std::println("Litere 'a' în \"banana\": {}", numara_litere("banana", 'a'));
}

Rezultat

x = 2, y = 1
Litere 'a' în "banana": 3

Exemplul 3: Referințe rvalue și std::move

Prima funcție este aleasă pentru variabile obișnuite, a doua pentru temporare și pentru obiecte date cu std::move. După mutare, text este într-o stare validă, dar nespecificată (aici, gol); nu te baza pe conținutul lui.

#include <print>
#include <string>
#include <utility>
#include <vector>

std::vector<std::string> arhiva;

void salveaza(const std::string& s) {
    std::println("copiez: {}", s);
    arhiva.push_back(s);
}

void salveaza(std::string&& s) {
    std::println("mut: {}", s);
    arhiva.push_back(std::move(s));
}

int main() {
    std::string text = "raport lunar";
    salveaza(text);
    salveaza(std::string("factură"));
    salveaza(std::move(text));
    std::println("Elemente în arhivă: {}, text gol: {}", arhiva.size(), text.empty());
}

Rezultat

copiez: raport lunar
mut: factură
mut: raport lunar
Elemente în arhivă: 3, text gol: true

Greșeli frecvente

  • Returnezi o referință la o variabilă locală (const std::string& f() { std::string s = "x"; return s; }). Variabila dispare la ieșirea din funcție; returnează prin valoare.
  • Ții o referință la un element dintr-un std::vector și apoi faci push_back. Vectorul își poate muta elementele în altă zonă de memorie, iar referința devine invalidă.
  • Folosești o variabilă după std::move ca și cum ar avea încă valoarea veche. După mutare, poți doar să-i dai o valoare nouă sau să o distrugi.

Comenzi înrudite

  • pointeri (*, &): Un pointer este o variabilă care ține adresa altei variabile; prin el poți citi și modifica valoarea de la acea adresă.
  • const: Marchează o valoare care nu mai poate fi modificată după inițializare; compilatorul oprește orice încercare de schimbare.
  • range-for (for (x : colecție)): Parcurge pe rând toate elementele unei colecții (vector, tablou, string, map) fără index și fără iteratori scriși de mână.
  • auto: Lasă compilatorul să deducă tipul variabilei din valoarea cu care este inițializată.
  • std::move: Mută elementele unui interval la o destinație, fără să le copieze (versiunea din <algorithm>).