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

160

J'ai vu ceci ici: Move Constructor appelant Move Constructor de classe de base

Quelqu'un pourrait-il expliquer:

  1. la différence entre std::moveet std::forward, de préférence avec quelques exemples de code?
  2. Comment y penser facilement et quand utiliser lequel
aCuria
la source
"Comment y penser facilement, et quand utiliser lequel" Vous utilisez movelorsque vous souhaitez déplacer une valeur, et forwardlorsque vous souhaitez utiliser un transfert parfait. Ce n'est pas sorcier ici;)
Nicol Bolas
move () effectue un cast inconditionnel alors que forward () effectue un cast basé sur le paramètre qui a passé.
RaGa__M

Réponses:

159

std::moveprend un objet et vous permet de le traiter comme temporaire (une rvalue). Bien que ce ne soit pas une exigence sémantique, généralement une fonction acceptant une référence à une rvalue l'invalidera. Lorsque vous voyez std::move, cela indique que la valeur de l'objet ne doit pas être utilisée par la suite, mais vous pouvez toujours attribuer une nouvelle valeur et continuer à l'utiliser.

std::forwarda un seul cas d'utilisation: pour 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) que l'appelant a utilisée pour le transmettre. Cela permet aux arguments rvalue d'être passés en tant que rvalues ​​et de lvalues ​​à passer en tant que lvalues, un schéma appelé «transfert parfait».

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 type de référence. Mais en dehors de ces cas d'utilisation spécifiques (qui couvrent 99,9% de l'utilité des casts de référence rvalue), vous devez utiliser static_castdirectement et écrire une bonne explication de ce que vous faites.

Potatoswatter
la source
Je ne suis pas sûr que ce soit exact, car std::forwardle seul cas d'utilisation est la transmission parfaite des arguments de fonction. J'ai rencontré des situations dans lesquelles je veux transmettre parfaitement d'autres choses, comme des membres d'objet.
Geoff Romer du
@GeoffRomer Membres d'un paramètre? Avez-vous un exemple?
Potatoswatter
J'ai posté un exemple comme question distincte: stackoverflow.com/questions/20616958/…
Geoff Romer
Hmm, donc si je comprends bien cela, cela signifie que je peux écrire une seule fonction forward () où forward (5) opère sur 5 comme si je le passais par valeur où forward (x) opère sur x comme si je le passais par référence sans avoir à écrire explicitement une surcharge.
iheanyi
@iheanyi Ouais, c'est l'idée! Mais ce doit être un modèle, pas seulement une fonction ordinaire.
Potatoswatter
63

Les deux std::forwardet std::movene sont que des moulages.

X x;
std::move(x);

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

std::move(make_X());

et dans ce cas c'est une fonction d'identité: prend une rvalue de type X et retourne une rvalue de type X.

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

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

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

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 des qualificatifs cv. 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 lvalue, le résultat sera une expression lvalue. Si Y n'est pas une référence lvalue, le résultat sera une expression rvalue (xvalue pour être précis).

forwardpeut prendre un argument rvalue uniquement si Y n'est pas une référence lvalue. Autrement dit, vous ne pouvez pas convertir une rvalue en lvalue. Ceci est pour des raisons de sécurité car cela conduit généralement à des références pendantes. Mais le cast d'une rvalue en rvalue est correct et autorisé.

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

Howard Hinnant
la source
Si je transfère parfaitement un objet à une fonction en utilisant std::forward, puis une fois cette fonction exécutée, puis-je utiliser cet objet? Je suis conscient que, dans le cas de std::move, c'est un comportement indéfini.
iammilind du
1
Concernant move: stackoverflow.com/a/7028318/576911 Car forward, si vous passez une lvalue, votre API doit réagir comme si elle recevait une lvalue. Normalement, cela signifie que la valeur ne sera pas modifiée. Mais s'il s'agit d'une valeur non constante, votre API l'a peut-être modifiée. Si vous transmettez une rvalue, cela signifie normalement que votre API peut en avoir été déplacée, et donc stackoverflow.com/a/7028318/576911 s'appliquerait.
Howard Hinnant
21

std::forwardest utilisé pour transmettre un paramètre exactement comme il a été passé à une fonction. Tout comme montré ici:

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

L'utilisation std::moveoffre un objet comme rvalue, pour éventuellement correspondre à un constructeur de déplacement ou à une fonction acceptant des rvalues. Il fait cela std::move(x)même si ce xn'est pas une valeur en soi.

Bo Persson
la source