Оригинальное название: Lvalue, Rvalue & Pitfalls
Исследуйте ссылки на lvalue и rvalue, поймите категории значений C++, изучите правила свертывания ссылок и ссылки пересылки и избегайте болтающихся ошибок ссылки.
Lvalue ссылки
lvalue reference (T&) — псевдоним, другое название существующего объекта. В отличие от указателей, ссылки * * должны быть инициализированы, * * не может * * быть недействительными, и * * не может * * быть повторно помещены для обозначения другого объекта.
Ссылки Lvalue связываются с lvalues — выражениями, имеющими идентичность (можно взять их адрес). Переменные, элементы массива и отнесенные указатели - все это lvalues. Ссылка неconst lvalue (int&) не может связываться с временным значением, потому что изменение временного почти всегда является ошибкой.
Lvalue базовые ссылки
Ссылки на Lvalue - это рабочая лошадка C++, используемая для ссылочной передачи, перегрузки оператора и предотвращения копий.
#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!"
}Ценностные категории в C++
C++ имеет точную таксономию категорий значений выражений. Понимание их имеет важное значение для семантики движения и идеальной пересылки:
lvalue — имеет идентичность, не может быть перемещен из неявно. Примеры: переменные, *ptr, arr[i], строковые буквы.
prvalue (чистая rvalue) — не имеет тождества, может быть перемещена из. Примеры: 42, x + y, std::string("temp"), функция возврата по значению.
xvalue (стоимость истечения срока действия) — имеет идентичность и может быть перемещена из. Примеры: std::move(x), static_cast, член rvalue.
glvalue (обобщенная lvalue) — lvalue или xvalue (имеет идентичность).
- rvalue - prvalue или xvalue (может быть перемещена с).
Ключевое понимание: эталонная переменная rvalue сама по себе является lvalue — у нее есть имя и адрес. Именно поэтому std::forward существует.
Ссылки на стоимость (T&&)
Ссылки на Rvalue связываются с rvalue (темпораторы и объекты std::move'd). Они являются основой семантики движения — они позволяют обнаружить, когда объект собирается быть уничтоженным, и украсть его ресурсы.
#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
}Ссылки Collapsing & Forwarding References
Когда ссылки объединяются в шаблоны, применяются правила reference collaping:
T& & обваливается до T&T& && обваливается до T&T&& & обваливается до T&T&& && обваливается до T&&
Правило простое: если любая ссылка является ссылкой на lvalue, результатом является ссылка на lvalue. Только && && дает &&.
передающая ссылка (также называемая универсальной ссылкой) - T&&, где T является приведенным параметром шаблона . Он может связываться как с lvalues, так и с rvalues. auto&& также является отправной точкой. Но std::vector НЕ является — T уже известен. const T&& также не является отправной точкой.
Ссылка dangling относится к разрушенному объекту. В отличие от висячих указателей, нет проверки nullptr — код будет бесшумно использовать данные о мусоре или сбоях.
Общие причины:
Возврат ссылки на локальную переменную — переменная разрушается при возвращении функции.
Захват по ссылке в лямбда, переживающий указанную переменную.const auto& временно внутри выражения — const auto& val = get_map()["key"] болтается, если get_map() возвращается по значению, потому что временная карта разрушается в полуколоне.
Временные жизни для петли над временной — for (auto& x : get_vector()) в порядке (временные жизни для петли), но for (auto& x : get_wrapper().get_vector()) может свисать, если обертка уничтожена.
Продление срока службы с помощью const T&
Ссылка на значение const может продлить срок службы временного, но только при прямой связи . Это не работает через вызовы функций или цепочки доступа к членам.
#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
}- Ссылки на Lvalue (
T&) являются псевдонимами — должны быть инициализированы, не могут быть аннулированы или повторно установлены. const T&может связываться с временными периодами и продлевать их срок службы (но только при непосредственной связи).- Ссылки на значения (
T&&) связываются с временными значениями и позволяют перемещать семантику. - Названная ссылка на rvalue сама по себе является lvalue — это важно для понимания.
- Ссылочное коллапсирование: любой
&в цепочке дает результат& - Передающие ссылки (
T&&с выводомT) связываются как с lvalues, так и с rvalues.
Тест — проверьте свои знания
(15 XP)1. Учитывая `void f(std::string&& s)`, какова категория значений `s` внутри `f`?
2. Какое правило обрушения ссылок является правильным?
3. Когда продление срока службы `const T&` работает на временные периоды?