Как std :: move () передает значения в RValues? - PullRequest
84 голосов
/ 22 сентября 2011

Я только что обнаружил, что не совсем понимаю логику 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 ]

140 голосов
/ 22 сентября 2011

Мы начнем с функции перемещения (которую я немного очистил):

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);
}
2 голосов
/ 22 сентября 2011

_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.

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

...