Как std::move() передает значения в RValues?
Я просто обнаружил, что не совсем понимаю логику std::move()
,
Сначала я гуглил это, но кажется, что есть только документы о том, как использовать std::move()
, а не как его структура работает.
Я имею в виду, я знаю, что функция-член шаблона, но когда я смотрю в std::move()
Определение в VS2010, это все еще сбивает с толку.
определение std::move() приведено ниже.
template<class _Ty> inline
typename tr1::_Remove_reference<_Ty>::_Type&&
move(_Ty&& _Arg)
{ // forward _Arg as movable
return ((typename tr1::_Remove_reference<_Ty>::_Type&&)_Arg);
}
Для меня сначала странным является параметр (_Ty&& _Arg), потому что когда я вызываю функцию, как вы видите ниже,
// main()
Object obj1;
Object obj2 = std::move(obj1);
это в основном равно
// std::move()
_Ty&& _Arg = Obj1;
Но, как вы уже знаете, вы не можете напрямую связать LValue со ссылкой на RValue, что заставляет меня думать, что так должно быть.
_Ty&& _Arg = (Object&&)obj1;
Однако это абсурдно, потому что std::move() должен работать для всех значений.
Так что я думаю, чтобы полностью понять, как это работает, я должен взглянуть и на эти структуры.
template<class _Ty>
struct _Remove_reference
{ // remove reference
typedef _Ty _Type;
};
template<class _Ty>
struct _Remove_reference<_Ty&>
{ // remove reference
typedef _Ty _Type;
};
template<class _Ty>
struct _Remove_reference<_Ty&&>
{ // remove rvalue reference
typedef _Ty _Type;
};
К сожалению, это все еще так запутанно, и я не понимаю.
Я знаю, что это все из-за отсутствия базовых навыков синтаксиса в C++. Я хотел бы знать, как они работают полностью, и любые документы, которые я могу получить в Интернете, будут более чем приветствоваться. (Если вы можете просто объяснить это, это тоже будет здорово).
2 ответа
Начнем с функции перемещения (которую я немного убрал):
template <typename T>
typename remove_reference<T>::type&& move(T&& arg)
{
return static_cast<typename remove_reference<T>::type&&>(arg);
}
Давайте начнем с более простой части - то есть, когда функция вызывается с помощью rvalue:
Object a = std::move(Object());
// Object() is temporary, which is prvalue
и наш move
шаблон создается следующим образом:
// move with [T = Object]:
remove_reference<Object>::type&& move(Object&& arg)
{
return static_cast<remove_reference<Object>::type&&>(arg);
}
поскольку remove_reference
новообращенные T&
в T
или же T&&
в T
, а также Object
это не ссылка, наша последняя функция:
Object&& move(Object&& arg)
{
return static_cast<Object&&>(arg);
}
Теперь вы можете задаться вопросом: нам вообще нужен актерский состав? Ответ: да, мы делаем. Причина проста; именованная ссылка rvalue рассматривается как lvalue (и неявное преобразование из lvalue в ссылку rvalue запрещено стандартом).
Вот что происходит, когда мы звоним move
с lvalue:
Object a; // a is lvalue
Object b = std::move(a);
и соответствующий move
конкретизации:
// move with [T = Object&]
remove_reference<Object&>::type&& move(Object& && arg)
{
return static_cast<remove_reference<Object&>::type&&>(arg);
}
Снова, remove_reference
новообращенные Object&
в Object
и мы получаем:
Object&& move(Object& && arg)
{
return static_cast<Object&&>(arg);
}
Теперь мы переходим к хитрой части: что делает Object& &&
даже значит, и как это может связать с lvalue?
Чтобы обеспечить идеальную пересылку, стандарт C++11 предоставляет специальные правила для свертывания ссылок, а именно:
Object & & = Object &
Object & && = Object &
Object && & = Object &
Object && && = Object &&
Как видите, по этим правилам Object& &&
на самом деле означает Object&
, которая является простой ссылкой lvalue, которая позволяет связывать lvalue.
Окончательная функция, таким образом:
Object&& move(Object& arg)
{
return static_cast<Object&&>(arg);
}
что мало чем отличается от предыдущего создания экземпляра с rvalue - они оба приводят его аргумент к ссылке на rvalue, а затем возвращают его. Разница в том, что первый экземпляр может использоваться только с r-значениями, а второй - с l-значениями.
Чтобы объяснить, зачем нам remove_reference
немного больше, давайте попробуем эту функцию
template <typename T>
T&& wanna_be_move(T&& arg)
{
return static_cast<T&&>(arg);
}
и создать его с помощью lvalue.
// wanna_be_move [with T = Object&]
Object& && wanna_be_move(Object& && arg)
{
return static_cast<Object& &&>(arg);
}
Применяя правила свертывания ссылок, упомянутые выше, вы можете увидеть, что мы получаем функцию, которую нельзя использовать, как move
(проще говоря, вы называете это с помощью lvalue, вы получаете lvalue обратно). Во всяком случае, эта функция является функцией идентичности.
Object& wanna_be_move(Object& arg)
{
return static_cast<Object&>(arg);
}
_Ty - это параметр шаблона, и в этой ситуации
Object obj1;
Object obj2 = std::move(obj1);
_Ty типа "Объект &"
именно поэтому _Remove_reference необходимо.
Было бы больше похоже
typedef Object& ObjectRef;
Object obj1;
ObjectRef&& obj1_ref = obj1;
Object&& obj2 = (Object&&)obj1_ref;
Если бы мы не удалили ссылку, это было бы так, как мы делали
Object&& obj2 = (ObjectRef&&)obj1_ref;
Но ObjectRef && сводится к Object &, который мы не могли связать с obj2.
Причина, по которой он сокращается таким образом, заключается в поддержке идеальной пересылки. Смотрите эту статью.