Як std :: move () передає значення в RValues?


104

Я просто виявив, що не повністю розумію логіку 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 ++. Мені хотілося б знати, як вони ретельно працюють і будь-які документи, які я можу отримати в Інтернеті, будуть більш ніж вітані. (Якщо ви можете просто пояснити це, це теж буде приголомшливо).


31
@NicolBolas Навпаки, саме запитання свідчить про те, що ОП недооцінює себе в цій заяві. Саме розуміння ОП основних навичок синтаксису дозволяє їм навіть ставити питання.
Кайл Странд

Я все одно не згоден - ви можете швидко навчитися або вже добре зрозуміти інформатику чи програмування в цілому, навіть на C ++, і все ще бороєтесь із синтаксисом. Краще витратити свій час на вивчення механіки, алгоритмів тощо, але покладатися на посилання, щоб розробити синтаксис у міру необхідності, ніж це бути технічно сформульованим, але мати неглибоке розуміння таких речей, як копіювання / переміщення / вперед. Як ви повинні знати, "коли і як використовувати std :: move, коли ви хочете перемістити конструювати щось", перш ніж ви знаєте, що рухається?
Джон П

Я думаю, що я прийшов сюди, щоб зрозуміти, як moveпрацює, а не як це реалізовано. Я вважаю це пояснення дуже корисним: pagefault.blog/2018/03/01/… .
Антон Данейко

Відповіді:


171

Почнемо з функції переміщення (яку я трохи прибрав):

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 буде розглядатися як іменують (і неявне перетворення з іменують в 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& &&переходимо до хитромудрої частини: що це означає і як це може бути пов'язане з значенням?

Щоб домогтися ідеального переадресації, стандарт C ++ 11 забезпечує спеціальні правила згортання посилань, які є наступними:

Object &  &  = Object &
Object &  && = Object &
Object && &  = Object &
Object && && = Object &&

Як бачите, під цими правилами Object& &&насправді мається на увазі Object&, що є простим посиланням на значення, яке дозволяє прив'язувати значення.

Кінцева функція, таким чином:

Object&& move(Object& arg)
{
  return static_cast<Object&&>(arg);
}

що не відрізняється від попередньої інстанції з rvalue - вони обидва подають свій аргумент, щоб оцінити посилання, а потім повернути його. Різниця полягає в тому, що перша інстанція може використовуватися лише з rvalues, а друга - з lvalues.


Щоб пояснити, для чого нам потрібно 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);
}

3
Гарна відповідь. Хоча я розумію, що для lvalue це гарна ідея оцінити Tяк Object&, я не знав, що це насправді зроблено. Я б очікував , що я також буду Tоцінювати це Objectв цьому випадку, оскільки я вважав, що це причина введення посилань на обгортку і std::ref, чи не так.
Крістіан Рау

2
Існує різниця між template <typename T> void f(T arg)(що є у статті у Вікіпедії) та template <typename T> void f(T& arg). Перший вирішує значення (і якщо ви хочете передати посилання, вам потрібно його обернути std::ref), а другий завжди вирішує посилання. На жаль, правила для виведення аргументів шаблону досить складні, тому я не можу надати точні міркування, для чого T&&перенаправляти Object& &&(але це відбувається насправді).
Вітус

1
Але чи є причина, що цей прямий підхід не працював би? template <typename T> T&& also_wanna_be_move(T& arg) { return static_cast<T&&>(arg); }
greggo

1
Це пояснює, чому Remove_reference необхідний, але я все ще не розумію, чому функція повинна приймати T&& (замість T&). Якщо я правильно розумію це пояснення, не має значення, отримаєте ви T&& або T&, оскільки в будь-якому випадку, Remove_reference передасть його T, тоді ви додасте &&. То чому б не сказати, що ви приймаєте T & (що семантично - це те, що ви приймаєте), а не T&& і покладатися на ідеальне переадресація, щоб дозволити абоненту пройти T &?
mgiuca

3
@mgiuca: Якщо ви хочете std::moveпризначати лише значенням rvalues, то так, T&було б добре. Цей трюк робиться здебільшого для гнучкості: ви можете зателефонувати std::moveна все (включені оцінки) та отримати назад оцінку.
Вітус

4

_Ty - параметр шаблону, і в цій ситуації

Object obj1;
Object obj2 = std::move(obj1);

_Ти є тип "Об'єкт &"

саме тому _Remove_reference необхідний.

Це було б більше схоже

typedef Object& ObjectRef;
Object obj1;
ObjectRef&& obj1_ref = obj1;
Object&& obj2 = (Object&&)obj1_ref;

Якби ми не видалили посилання, це було б так, як ми це робили

Object&& obj2 = (ObjectRef&&)obj1_ref;

Але ObjectRef && зводиться до Object &, який ми не могли прив’язати до obj2.

Причина, якою він зменшує цей спосіб, - підтримка ідеального переадресації. Дивіться цей документ .


Це не пояснює все, чому _Remove_reference_це необхідно. Наприклад, якщо у вас є Object&typedef, і ви посилаєтесь на нього, ви все одно отримаєте Object&. Чому це не працює з &&? На це є відповідь, і це пов'язано з ідеальним переадресацією.
Нікол Болас

2
Правда. І відповідь дуже цікава. А && зводиться до A &, тому якщо ми спробуємо використовувати (ObjectRef &&) obj1_ref, ми отримаємо Object & замість цього випадку.
Вон Катон
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.