Перейти к содержимому

Оригинальное название: Lvalue, Rvalue & Pitfalls

Исследуйте ссылки на lvalue и rvalue, поймите категории значений C++, изучите правила свертывания ссылок и ссылки пересылки и избегайте болтающихся ошибок ссылки.

Lvalue ссылки

lvalue reference (T&) — псевдоним, другое название существующего объекта. В отличие от указателей, ссылки * * должны быть инициализированы, * * не может * * быть недействительными, и * * не может * * быть повторно помещены для обозначения другого объекта.

Ссылки Lvalue связываются с lvalues — выражениями, имеющими идентичность (можно взять их адрес). Переменные, элементы массива и отнесенные указатели - все это lvalues. Ссылка неconst lvalue (int&) не может связываться с временным значением, потому что изменение временного почти всегда является ошибкой.

Lvalue базовые ссылки

Ссылки на Lvalue - это рабочая лошадка C++, используемая для ссылочной передачи, перегрузки оператора и предотвращения копий.

lvalue_refs.cpp
#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(x), член rvalue.
glvalue (обобщенная lvalue) — lvalue или xvalue (имеет идентичность).
- rvalue - prvalue или xvalue (может быть перемещена с).

Ключевое понимание: эталонная переменная rvalue сама по себе является lvalue — у нее есть имя и адрес. Именно поэтому std::forward существует.

Ссылки на стоимость (T&&)

Ссылки на Rvalue связываются с rvalue (темпораторы и объекты std::move'd). Они являются основой семантики движения — они позволяют обнаружить, когда объект собирается быть уничтоженным, и украсть его ресурсы.

rvalue_refs.cpp
#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 может продлить срок службы временного, но только при прямой связи . Это не работает через вызовы функций или цепочки доступа к членам.

lifetime_extension.cpp
#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&` работает на временные периоды?