Reference: Lvalue, Rvalue & Pitfalls
Prozkoumejte lvalue a rvalue reference, porozumějte hodnotovým kategoriím C++, naučte se pravidla srážení referencí a přeposílání referencí a vyhněte se visícím referenčním chybám.
Lvalue References
lvalue reference (T&) je alias — jiný název pro existující objekt. Na rozdíl od ukazatelů musí být odkazy musí inicializovat, nesmí být null a nelze je znovu usadit, aby odkazovaly na jiný objekt.
Odkazy Lvalue se vážou na lvalues — výrazy, které mají identitu (můžete si vzít jejich adresu). Proměnné, prvky pole a dereferencované ukazatele jsou všechny lvalues. Nekonstantní odkaz lvalue (int&) se nemůže vázat na dočasný (rvalue), protože úprava dočasnosti je téměř vždy chybou.
Lvalue Reference Basics
Lvalue reference jsou tahounem C++ – používají se pro pass-by-reference, přetěžování operátorů a zamezení kopírování.
#include <iostream>
#include <string>
void append_greeting(std::string& s) {
s += ", welcome!"; // modifies the caller's string
}
int main() {
int x = 10;
int& ref = x; // ref is an alias for x
ref = 20;
std::cout << x << '\n'; // 20 — x was modified through ref
// const reference can bind to a temporary (rvalue)
const std::string& greeting = std::string("Hello");
// The temporary's lifetime is extended to match greeting's scope
std::cout << greeting << '\n'; // "Hello"
// Non-const lvalue reference CANNOT bind to rvalue:
// std::string& bad = std::string("oops"); // COMPILER ERROR
std::string name = "Alice";
append_greeting(name);
std::cout << name << '\n'; // "Alice, welcome!"
}Hodnotové kategorie v C++
C++ má přesnou taxonomii kategorií hodnot výrazu. Jejich porozumění je nezbytné pro sémantiku pohybu a dokonalé předávání:
- lvalue — má identitu, nelze ji implicitně přesunout. Příklady: proměnné,*ptr,arr[i], řetězcové literály.
- prvalue (čistá rvalue) — nemá žádnou identitu, lze ji přesunout. Příklady:42,x + y,std::string("temp"), funkce vracející hodnotu.
- xvalue (hodnota s vypršením platnosti) — má identitu A lze ji přesunout. Příklady:std::move(x),static_cast, člen rvalue.
- glvalue (zobecněná lvalue) — lvalue nebo xvalue (má identitu).
- rvalue — prvalue nebo xvalue (lze přesunout z).
Klíčový poznatek: referenční proměnná rvalue je sama o sobě lvalue – má jméno a adresu. To je zdrojem mnoha zmatků a právě proto existujestd::forward.
Reference Rvalue (T&&)
Reference Rvalue se vážou k rvalues (dočasné objekty astd::move'd objekty). Jsou základem sémantiky pohybu – umožňují vám zjistit, kdy má být objekt zničen, a ukrást jeho zdroje.
#include <iostream>
#include <string>
#include <utility> // std::move
void process(const std::string& s) {
std::cout << "lvalue: " << s << '\n';
}
void process(std::string&& s) {
std::cout << "rvalue: " << s << '\n';
// s is an lvalue inside this function (it has a name!)
// To move from s, you'd need std::move(s)
}
int main() {
std::string a = "hello";
process(a); // calls lvalue overload
process(std::string("world")); // calls rvalue overload
process(std::move(a)); // calls rvalue overload — a is now moved-from
// IMPORTANT: a is in a valid but unspecified state
// Only safe operations: assign to it, destroy it
std::cout << "a after move: '" << a << "'\n"; // likely empty
}Sbalení referencí a předání referencí
Když se odkazy spojí v šablonách, platí pravidla sbalení referencí:
-T& &se zhroutí naT&
-T& &&se zhroutí naT&
-T&& &se zhroutí naT&
-T&& &&se zhroutí naT&&
Pravidlo je jednoduché: pokud je kterýkoli odkaz odkazem lvalue, výsledkem je odkaz lvalue. Pouze&& &&dává&&.
Přesměrovací odkaz (také nazývaný univerzální odkaz) jeT&&, kdeTje odvozený parametr šablony. Může se vázat jak na lvalues, tak na rvalues.auto&&je také reference pro předávání. Alestd::vectorNENÍ —Tje již znám.const T&&také není odkaz na přesměrování.
visící odkaz odkazuje na zničený objekt. Na rozdíl od visících ukazatelů zde není žádná kontrolanullptr– kód tiše použije nesmyslná data nebo se zhroutí.
Běžné příčiny:
- Vrácení odkazu na lokální proměnnou — proměnná je zničena, když se funkce vrátí.
- Zachycení odkazem v lambda, která přežije odkazovanou proměnnou.
- const auto&na dočasný uvnitř výrazu —const auto& val = get_map()["key"]visí, pokudget_map()vrátí hodnotu, protože dočasná mapa je zničena středníkem.
- Dočasná smyčka for založená na rozsahu —for (auto& x : get_vector())je v pořádku (dočasná životnost smyčky), alefor (auto& x : get_wrapper().get_vector())může viset, pokud je obal zničen.
Doživotní prodloužení s const T&
Referenceconstlvalue může prodloužit životnost dočasnosti, ale pouze když je přímo vázaná. Toto NEFUNGUJE prostřednictvím volání funkcí nebo řetězců přístupu členů.
#include <iostream>
#include <string>
std::string make_greeting() { return "Hello, World!"; }
const std::string& identity(const std::string& s) { return s; }
int main() {
// SAFE: const ref directly bound to temporary
const std::string& good = make_greeting();
std::cout << good << '\n'; // OK — lifetime extended
// DANGEROUS: lifetime extension does NOT propagate through functions
const std::string& bad = identity(make_greeting());
// bad is DANGLING — the temporary was destroyed after identity() returned
// std::cout << bad << '\n'; // UNDEFINED BEHAVIOR
// SAFE: just use a value
std::string safe = make_greeting(); // move or copy-elision
std::cout << safe << '\n'; // always safe
}- Odkazy na Lvalue (
T&) jsou aliasy – musí být inicializovány, nemohou být nulové nebo znovu usazené const T&can bind to temporaries and extends their lifetime (but only when directly bound)- Reference Rvalue (
T&&) se vážou na provizorní položky a umožňují sémantiku přesunu - A named rvalue reference is itself an lvalue — this is critical to understand
- Reference collapsing: any
&in the chain makes the result& - Forwarding references (
T&&with deducedT) bind to both lvalues and rvalues
Kvíz — Otestujte své znalosti
(15 XP)1. Jaká je vzhledem k`void f(std::string&& s)`hodnotová kategorie`s`uvnitř`f`?
2. Které pravidlo sbalení referencí je správné?
3. Kdy funguje prodloužení životnosti`const T&`pro dočasné?