web-dev-qa-db-fra.com

Quelle est la différence entre std :: move et std :: forward

J'ai vu ceci ici: Move Constructor appelle le constructeur de base Move Constructor

Quelqu'un pourrait-il expliquer:

  1. la différence entre std::move et std::forward , de préférence avec quelques exemples de code?
  2. Comment y penser facilement, et quand utiliser lequel
152
aCuria

std::move prend un objet et vous permet de le traiter comme une valeur temporaire (une valeur). Bien qu'il ne s'agisse pas d'une exigence sémantique, une fonction acceptant une référence à une valeur rvalue l'invalidera. Quand tu vois std::move, cela indique que la valeur de l'objet ne doit plus être utilisée, mais vous pouvez toujours affecter une nouvelle valeur et continuer à l'utiliser.

std::forward a un cas d'utilisation unique: convertir un paramètre de fonction basé sur un modèle (à l'intérieur de la fonction) dans la catégorie de valeur (lvalue ou rvalue) utilisée par l'appelant pour le transmettre. Cela permet de transmettre les arguments rvalue en tant que rvalues ​​et les lvalues ​​en tant que lvalues, un schéma appelé "transmission parfaite".

Pour illustrer :

void overloaded( int const &arg ) { std::cout << "by lvalue\n"; }
void overloaded( int && arg ) { std::cout << "by rvalue\n"; }

template< typename t >
/* "t &&" with "t" being template param is special, and  adjusts "t" to be
   (for example) "int &" or non-ref "int" so std::forward knows what to do. */
void forwarding( t && arg ) {
    std::cout << "via std::forward: ";
    overloaded( std::forward< t >( arg ) );
    std::cout << "via std::move: ";
    overloaded( std::move( arg ) ); // conceptually this would invalidate arg
    std::cout << "by simple passing: ";
    overloaded( arg );
}

int main() {
    std::cout << "initial caller passes rvalue:\n";
    forwarding( 5 );
    std::cout << "initial caller passes lvalue:\n";
    int x = 5;
    forwarding( x );
}

Comme le mentionne Howard, il existe également des similitudes car ces deux fonctions sont simplement converties en types de référence. Mais en dehors de ces cas d'utilisation spécifiques (qui couvrent 99,9% de l'utilité des conversions de valeur de valeur), vous devez utiliser static_cast directement et écrivez une bonne explication de ce que vous faites.

144
Potatoswatter

Tous les deux std::forward et std::move ne sont que des moulages.

X x;
std::move(x);

Ce qui précède convertit l'expression lvalue x de type X en une expression rvalue de type X (une valeur x pour être exacte). move peut également accepter une valeur rvalue:

std::move(make_X());

et dans ce cas, il s’agit d’une fonction d’identité: prend une valeur de type X et renvoie une valeur de type X.

Avec std::forward vous pouvez sélectionner la destination dans une certaine mesure:

X x;
std::forward<Y>(x);

Convertit l'expression lvalue x de type X en une expression de type Y. Il existe des contraintes sur ce que peut être Y.

Y peut être une base accessible de X ou une référence à une base de X. Y peut être X ou une référence à X. On ne peut pas rejeter les qualificatifs cv avec forward, mais on peut ajouter cv -qualificateurs. Y ne peut pas être un type simplement convertible à partir de X, sauf via une conversion de base accessible.

Si Y est une référence de lvalue, le résultat sera une expression de lvalue. Si Y n'est pas une référence lvalue, le résultat sera une expression rvalue (xvalue pour être précise).

forward peut prendre un argument rvalue uniquement si Y n'est pas une référence lvalue. Autrement dit, vous ne pouvez pas lancer de valeur sur lvalue. C'est pour des raisons de sécurité, car cela conduit souvent à des références en suspens. Mais attribuer une valeur à valeur est correct et autorisé.

Si vous essayez de spécifier Y avec quelque chose qui n'est pas autorisé, l'erreur sera interceptée lors de la compilation, pas lors de l'exécution.

60
Howard Hinnant

std::forward Est utilisé pour transmettre un paramètre exactement de la manière dont il a été transmis à une fonction. Juste comme montré ici:

Quand utiliser std :: forward pour transférer les arguments?

Utiliser std::move Offre un objet sous forme de valeur rvalue, pour éventuellement correspondre à un constructeur de déplacement ou à une fonction acceptant des valeurs rvalues. C'est le cas pour std::move(x) même si x n'est pas une valeur rvalue en soi.

21
Bo Persson