Что такое rvalues, lvalues, xvalues, glvalues и prvalues в C++?
Основные категории значений в C++11 и новее
Вы правы: в C++11 классификация стала сложнее из-за move-семантики. Вместо двух категорий (lvalue/rvalue) появилось пять. Они образуют иерархию:
- lvalue (locator value) — выражение, которое ссылается на объект с определённым адресом в памяти.
- prvalue (pure rvalue) — выражение, которое не имеет адреса (временные объекты, литералы, результаты арифметики).
- xvalue (eXpiring value) — выражение, которое ссылается на объект, чьи ресурсы могут быть "украдены" (например, результат std::move).
- glvalue (generalized lvalue) = lvalue + xvalue (все, у кого есть адрес).
- rvalue = prvalue + xvalue (все, что можно перемещать).
Как это работает на практике
Рассмотрим ваш пример:
int x = 10; // x — lvalue (есть адрес)
int y = x + 5; // x+5 — prvalue (временный результат)
int&& z = std::move(x); // std::move(x) — xvalue
std::move(x) не перемещает данные, а просто преобразует lvalue в xvalue. xvalue — это "умирающее" значение: оно имеет адрес (как lvalue), но его можно переместить (как rvalue). Благодаря этому компилятор может выбрать конструктор перемещения вместо копирования.
Пример для понимания:
std::string a = "hello";
std::string b = std::move(a); // a — lvalue, но std::move(a) — xvalue
// После этого a пуста (её строка перемещена в b)
Здесь std::move(a) — xvalue, потому что:
- У a есть адрес (как у lvalue).
- Но мы явно говорим компилятору: "это значение можно переместить" (как у rvalue).
Правила привязки ссылок:
- lvalue-ссылка (T&) может привязаться только к lvalue.
- rvalue-ссылка (T&&) может привязаться к rvalue (prvalue или xvalue).
int&& r1 = 42; // 42 — prvalue, OK
int&& r2 = std::move(x); // xvalue, OK
int& r3 = x; // x — lvalue, OK
int& r4 = 42; // Ошибка: 42 — prvalue
Итоговая таблица:
| Категория | Пример | Имеет адрес? | Можно переместить? |
|---|---|---|---|
| lvalue | x, a[0] | Да | Нет (без std::move) |
| prvalue | 42, x+y, std::string("temp") | Нет | Да |
| xvalue | std::move(x), static_cast<int&&>(x) | Да | Да |
| glvalue | lvalue + xvalue | Да | — |
| rvalue | prvalue + xvalue | — | Да |
Таким образом, xvalue — это мост между lvalue и rvalue. Он позволяет "перемещать" объекты, которые обычно считаются lvalue, если вы явно разрешаете это через std::move.
Альтернативный взгляд: думайте о времени жизни
Можно подойти к категориям с точки зрения того, когда объект будет уничтожен:
- lvalue — объект живёт дольше текущего выражения (обычно до конца блока).
- prvalue — объект умрёт в конце текущего выражения (если не привязан к ссылке).
- xvalue — объект живёт, но мы "разрешаем" ему умереть (через std::move).
Например, временный объект std::string("temp") — prvalue. Если вы напишете:
std::string s = std::string("temp"); // prvalue, будет перемещён или скопирован
В C++11 компилятор может "украсть" ресурсы у временного объекта, потому что он всё равно будет уничтожен.
А если вы пишете:
std::string s2 = std::move(s); // xvalue — "умирающее" lvalue
Вы говорите: "я разрешаю переместить данные из s, даже если s ещё жив". После этого s остаётся в "пустом" состоянии.
Таким образом, prvalue — это временные объекты, которые не имеют имени, а xvalue — это именованные объекты, которые вы временно "превратили" во временные. Это помогает понять, почему move-семантика работает эффективно: вы явно указываете, что объект можно "разобрать на запчасти".
Ответов пока нет.
Войдите чтобы оставить ответ