web-dev-qa-db-fra.com

Comment std :: move () transfère-t-il des valeurs dans RValues?

Je viens de découvrir que je ne comprenais pas parfaitement la logique de std::move().

Au début, je l'ai recherché sur Google, mais il semble qu'il n'y ait que des documents sur la façon d'utiliser std::move(), pas sur la façon dont sa structure fonctionne.

Je veux dire, je sais ce qu'est la fonction membre du modèle mais quand je regarde dans la définition de std::move() dans VS2010, c'est toujours déroutant.

la définition de std :: move () va ci-dessous.

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

Ce qui est bizarre en premier pour moi, c'est le paramètre, (_Ty && _Arg), parce que quand j'appelle la fonction comme vous le voyez ci-dessous,

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

il est essentiellement égal à

// std::move()
_Ty&& _Arg = Obj1;

Mais comme vous le savez déjà, vous ne pouvez pas lier directement une LValue à une référence RValue, ce qui me fait penser que cela devrait être comme ça.

_Ty&& _Arg = (Object&&)obj1;

Cependant, c'est absurde car std :: move () doit fonctionner pour toutes les valeurs.

Je suppose donc que pour bien comprendre comment cela fonctionne, je devrais également jeter un œil à ces structures.

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

Malheureusement, c'est toujours aussi déroutant et je ne comprends pas.

Je sais que tout cela est dû à mon manque de compétences de base en syntaxe sur C++. Je voudrais savoir comment cela fonctionne à fond et tous les documents que je peux obtenir sur Internet seront les bienvenus. (Si vous pouvez simplement expliquer cela, ce sera génial aussi).

87
Dean Seo

Nous commençons par la fonction de déplacement (que j'ai nettoyée un peu):

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

Commençons par la partie la plus simple, c'est-à-dire lorsque la fonction est appelée avec rvalue:

Object a = std::move(Object());
// Object() is temporary, which is prvalue

et notre modèle move est instancié comme suit:

// move with [T = Object]:
remove_reference<Object>::type&& move(Object&& arg)
{
  return static_cast<remove_reference<Object>::type&&>(arg);
}

Puisque remove_reference convertit T& à T ou T&& à T, et Object n'est pas une référence, notre fonction finale est:

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

Maintenant, vous vous demandez peut-être: avons-nous même besoin du casting? La réponse est: oui, nous le faisons. La raison est simple; nommé rvalue reference is traité comme lvalue (et la conversion implicite de lvalue en rvalue référence est interdite par la norme).


Voici ce qui se passe lorsque nous appelons move avec lvalue:

Object a; // a is lvalue
Object b = std::move(a);

et l'instanciation move correspondante:

// move with [T = Object&]
remove_reference<Object&>::type&& move(Object& && arg)
{
  return static_cast<remove_reference<Object&>::type&&>(arg);
}

Encore, remove_reference convertit Object& à Object et nous obtenons:

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

Nous arrivons maintenant à la partie délicate: qu'est-ce que Object& && signifie même et comment peut-il se lier à lvalue?

Pour permettre un transfert parfait, la norme C++ 11 fournit des règles spéciales pour le regroupement des références, qui sont les suivantes:

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

Comme vous pouvez le voir, selon ces règles Object& && signifie en fait Object&, qui est une simple référence lvalue qui permet de lier des valeurs l.

La fonction finale est donc:

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

ce qui n'est pas différent de l'instanciation précédente avec rvalue - ils convertissent tous les deux son argument en référence à rvalue puis le renvoient. La différence est que la première instanciation ne peut être utilisée qu'avec rvalues, tandis que la seconde fonctionne avec lvalues.


Pour expliquer pourquoi avons-nous besoin de remove_reference un peu plus, essayons cette fonction

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

et l'instancier avec lvalue.

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

En appliquant les règles de réduction de référence mentionnées ci-dessus, vous pouvez voir que nous obtenons une fonction inutilisable comme move (pour le dire simplement, vous l'appelez avec lvalue, vous récupérez lvalue). Si quoi que ce soit, cette fonction est la fonction d'identité.

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

_Ty est un paramètre de modèle, et dans cette situation

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

_Ty est de type "Objet &"

c'est pourquoi la référence _Remove_ est nécessaire.

Ce serait plus comme

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

Si nous ne supprimions pas la référence, ce serait comme si nous faisions

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

Mais ObjectRef && se réduit à Object &, que nous ne pouvions pas lier à obj2.

La raison pour laquelle il réduit cette façon est de prendre en charge un transfert parfait. Voir ce document .

3
Vaughn Cato