До прихода C++11
lvalue- это то, что может стоять слева от оператора присваивания.rvalue- это "временные объекты", им нельзя что-то присваивать. Кажется, что у них нельзя было взять адрес.
С приходом C++11 появилась move-семантика, из-за чего схема стала древовидной и усложнилась в понимании.
Сейчас
Введем пару понятий:
- Наличие идентичности (identity) – наличие какого-то параметра, по которому можно понять, ссылаются ли два выражения на одну и ту же сущность (например, адрес в памяти)
- Возможность перемещения (можно ли объект переместить, помувать)
Выражения сейчас делятся на два больших типа:
glvalue- обладают идентичностьюrvalue- могут быть перемещены
Эти категории распадаются в сумме на три (одна у них общая).
-
glvaluelvalue- обладают идентичностью, но не могут быть перемещеныxvalue- обладают идентичностью, могут быть перемещены
-
rvaluexvalue- то же самое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 и его друзей (когда они возможны).
Заимствования: