web-dev-qa-db-ja.com

std :: move()はどのように値をRValuesに転送しますか?

std::move()のロジックを完全に理解していないことに気づきました。

最初はグーグルで検索しましたが、std::move()の使用方法に関するドキュメントのみがあり、その構造のしくみはありません。

つまり、テンプレートメンバ関数が何であるかはわかっていますが、VS2010のstd::move()定義を調べると、それはまだ混乱しています。

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++に関する基本的な構文スキルの不足によるものです。これらがどのように機能するかを知りたいと思います。インターネットで入手できるドキュメントは大歓迎です。 (これを説明することができれば、それも素晴らしいでしょう)。

87
Dean Seo

Move関数から始めます(これを少し整理しました)。

template <typename T>
typename remove_reference<T>::type&& move(T&& arg)
{
  return static_cast<typename remove_reference<T>::type&&>(arg);
}

簡単な部分から始めましょう-つまり、関数が右辺値で呼び出された場合:

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);
}

さて、あなたは不思議に思うかもしれません:キャストさえ必要ですか?答えは、そうです。その理由は簡単です。名前付き右辺値参照is左辺値として扱われます(左辺値から右辺値参照への暗黙的な変換は標準では禁止されています)。


moveを左辺値で呼び出すと、次のようになります。

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);
}

これは、右辺値を使用した以前のインスタンス化とは異なります-引数を右辺値参照にキャストしてから返します。違いは、最初のインスタンス化は右辺値でのみ使用でき、2番目のインスタンス化は左辺値で使用できることです。


理由を説明するには、remove_referenceもう少し、この機能を試してみましょう

template <typename T>
T&& wanna_be_move(T&& arg)
{
  return static_cast<T&&>(arg);
}

そして、それを左辺値でインスタンス化します。

// wanna_be_move [with T = Object&]
Object& && wanna_be_move(Object& && arg)
{
  return static_cast<Object& &&>(arg);
}

上記の参照折りたたみルールを適用すると、moveとして使用できない関数を取得できます(簡単に言うと、左辺値で呼び出して左辺値を取得します)。どちらかといえば、この関数は恒等関数です。

Object& wanna_be_move(Object& arg)
{
  return static_cast<Object&>(arg);
}
148
Vitus

_Tyはテンプレートパラメータであり、この状況では

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

_Tyは「Object&」タイプです

これが_Remove_referenceが必要な理由です。

もっと似ているだろう

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

参照を削除しなかった場合は、実行しているようになります

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

しかし、ObjectRef &&はObject&になり、obj2にバインドできませんでした。

この方法を減らす理由は、完全な転送をサポートするためです。 このペーパー を参照してください。

3
Vaughn Cato