До прихода C++11
lvalue
- это то, что может стоять слева от оператора присваивания.rvalue
- это "временные объекты", им нельзя что-то присваивать. Кажется, что у них нельзя было взять адрес.
С приходом C++11 появилась move-семантика, из-за чего схема стала древовидной и усложнилась в понимании.
Сейчас
Введем пару понятий:
- Наличие идентичности (identity) – наличие какого-то параметра, по которому можно понять, ссылаются ли два выражения на одну и ту же сущность (например, адрес в памяти)
- Возможность перемещения (можно ли объект переместить, помувать)
Выражения сейчас делятся на два больших типа:
glvalue
- обладают идентичностьюrvalue
- могут быть перемещены
Эти категории распадаются в сумме на три (одна у них общая).
-
glvalue
lvalue
- обладают идентичностью, но не могут быть перемещеныxvalue
- обладают идентичностью, могут быть перемещены
-
rvalue
xvalue
- то же самоеprvalue
- не обладают идентичностью, могут быть перемещены
Да кто такие эти ваши glvalue и rvalue
Свойства glvalue
:
- Могут быть неявно преобразованы в
prvalue
- Могут быть полиморфными (?)
- Не могут иметь тип
void
(из-за наличия идентичности) - Может быть неполным типом (
incomplete type
), где это разрешено выражением
Свойства rvalue
:
- Нельзя взять адрес оператором & (из-за отсутствия идентичности)
- Не могут находиться слева от оператора =
- Могут использоваться для инициализации
const lvalue
-ссылки илиrvalue
-ссылки, при этом время жизни объекта расширяется до времени жизни ссылки - Если в overload resolution пришли две функции, одна из которых принимает
const-lvalue
-ссылку, а другаяrvalue
-ссылку, то выберется вторая (то есть, например, если move-конструктор определен, то он предпочтительнее)
Про lvalue
Свойства:
- Все свойства
glvalue
- Можно взять адрес оператором &
- Модифицируемые
lvalue
могут стоять слева от оператора присваивания - Могут использоваться для инициализации
lvalue
-ссылки
Примеры lvalue
:
- Имя переменной, функции или поле класса любого типа. Даже если переменная является
rvalue
-ссылкой, имя этой переменной в выражении являетсяlvalue
(другими словами, именованнаяrvalue
-ссылка являетсяlvalue
-значением, но это все ещеrvalue
-ссылка) - Вызов функции или оператора, возвращающего
lvalue
-ссылку - Преобразование к типу
lvalue
-ссылки - Результат встроенных операторов присваивания, составных операторов присваивания (
=
,+=
,/=
и т.д.), встроенных преинкремента и предекремента, встроенных операторов разыменования указателя - Результат встроенного оператора обращения по индексу от
lvalue
-массива - Строковый литерал, например,
"Hello world!"
(можно взять его адрес тоже)
Про prvalue
Свойства:
- Все свойства
rvalue
- Не могут быть полиморфными (?)
- Не могут быть неполного типа
- Не могут иметь абстрактный тип или быть массивом элементов абстрактного типа
Примеры prvalue
:
- Литерал (кроме строкового), например,
42
,false
илиnullptr
- Вызов функции или оператора, который возвращает не ссылку
- Результат преобразования к нессылочному типу
- Результат встроенных операторов постинкремента и постдекремента, встроенных математических, логических операторов, операторов сравнения, взятия адреса
- Указатель
this
- Элемент перечисления (enum)
- Нетиповой параметр шаблона, если он не является классом
- Лямбда-выражение (пример,
[](int x) { return x*x; }
)
Про xvalue
Свойства:
- Все свойства
glvalue
- Все свойства
rvalue
Примеры xvalue
:
- Вызов функции или встроенного оператора, возвращающего
rvalue
-ссылку, напримерstd::move(x)
- Результат преобразования к
rvalue
-ссылке - Нестатический член класса от
rvalue
-объекта
std::move()
Функция std::move()
не выполняет никаких перемещений, о чем порой заблуждаются - это просто приведение lvalue
-аргумента к rvalue
-ссылке.
Может быть реализована следующим образом:
template <class T>
constexpr remove_reference_t<T>&& move(T&& arg) noexcept {
return static_cast<remove_reference_t<T>&&>(arg);
}
То есть это просто обертка над static_cast
, которая «убирает» ссылку у переданного аргумента с помощью remove_reference_t
и, добавив &&, преобразует тип в rvalue
-ссылку.
В примере выше T&& - не rvalue
-ссылка, а универсальная ссылка, о чем будет сказано в отдельном разделе.
Еще одна типичная ошибка при использовании std::move()
:
std::string get_my_string(const size_t index) {
std::string my_string;
// *do something*
return std::move(my_string); // wrong!
}
Работать это будет, но это неэффективно. Лучше писать без std::move()
- копирования не случится из-за оптимизации RVO
и его друзей (когда они возможны).
Заимствования: