Как 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.

Причина, по которой он сокращается таким образом, заключается в поддержке идеальной пересылки. Смотрите эту статью.

Другие вопросы по тегам