rvalue-ссылки и std::move()
Файл заголовков: #include <utility>
Для начала стоит вспомнить что выражения в С++ можно поделить на два типа:
- lvalue - все что имеет имя (либо адрес в памяти), а значит может быть указано слева от знака = (например переменная)
- rvalue - все что не имеет имени (соответственно адреса в памяти) (например: значение, которое возвращает функция, вызов конструктора либо литерал), тоесть временные значения
Указатели могут указывать только на lvalue!
Начиная с С++11 ссылки тоже бывают двух видов:
- lvalue-ссылки - могут указывать только на lvalue (
int &b = a;
) либо на rvalue при условии если ссылка const (const int &b = 5;
) - rvalue-ссылки (C++11) - могут связываться только с rvalue (
int && d = 5;
)
Преобразование ссылок в шаблонах
До стандарта С++11 если в результате инстанцирования шаблона получался тип, который являлся ссылкой на ссылку то это приводило бы к ошибке компиляции. В С++11 появились правила “склейки”.
Правила “склейки” ссылок:
- T& & -> T&
- T& && -> T&
- T&& & -> T&
- T&& && -> T&&
Таким образом можно создать универсальную ссылку:
template<typename T>
void foo(T && t) {}
Если передать функции:
- lvalue, то тип T будет выведён как lvalue-ссылка и по правилам склейки это все превратиться в обычную lvalue-ссылку (A& && -> A &)
- rvalue, то тип T будет выведён как rvalue-ссылка и по правилам склейки это все превратиться в rvalue-ссылку (A&& && -> A&&)
std::move
std::move()
это шаблонная функция, которая определена в стандартной библиотеке.
Упрощенное определение std::move()
:
template<class T>
typename remove_reference<T>::type&& move(T && a)
{
typedef typename remove_reference<T>::type&& RvalRef;
return static_cast<RvalRef>(a);
}
1. std::move
шаблонная функция, которая принимает rvalue-ссылку, соответственно удовлетворяет требованиям универсальной ссылки
2. Возвращаемый тип:
- typename - облегчает работу компилятору, говоря что выражение после это какой-то тип
- __remove_reference
__ - метафункция, которая удаляет ссылку (A& -> A, A&& -> A) - ::type&& - приписывает rvalue-ссылку (A -> A&&)
Если бы мы сразу приводили lvalue-ссылку к rvalue-ссылке, то по правилам склеивания получили бы lvalue-ссылку (A& && -> A&).
Получается все что делает std::move()
- static_cast lvalue к rvalue