Dans ce cas
struct Foo {};
Foo meh() {
return std::move(Foo());
}
Je suis à peu près sûr que le déménagement est inutile, car le Foo
nouvellement créé sera une xvalue.
Mais quoi dans des cas comme ceux-ci?
struct Foo {};
Foo meh() {
Foo foo;
//do something, but knowing that foo can safely be disposed of
//but does the compiler necessarily know it?
//we may have references/pointers to foo. how could the compiler know?
return std::move(foo); //so here the move is needed, right?
}
Là le déménagement est nécessaire, je suppose?
Dans le cas de return std::move(foo);
, le move
est superflu à cause de 12.8/32:
Lorsque les critères d'élision d'une opération de copie sont remplis ou le seraient, à l'exception du fait que l'objet source est un paramètre de fonction et que l'objet à copier est désigné par une valeur lvalue, la résolution de surcharge pour sélectionner le constructeur de la copie est d'abord effectué comme si l'objet était désigné par une valeur.
return foo;
Est un cas d'OVNR, donc la copie est autorisée. foo
est une lvalue. Ainsi, le constructeur sélectionné pour la "copie" de foo
vers la valeur de retour de meh
doit être le constructeur de déplacement, s'il en existe un.
L'ajout de move
a toutefois un effet potentiel: il empêche le déplacement d'être supprimé, car return std::move(foo);
est non éligible pour un OVNR.
Autant que je sache, 12.8/32 énonce les niquement conditions dans lesquelles une copie d'une valeur lvalue peut être remplacée par un déplacement. En règle générale, le compilateur n'est pas autorisé à détecter qu'une lvalue est inutilisée après la copie (à l'aide de DFA, par exemple) et à effectuer la modification de sa propre initiative. Je suppose ici qu'il y a une différence observable entre les deux - si le comportement observable est le même, la règle "as-if" s'applique.
Donc, pour répondre à la question dans le titre, utilisez std::move
Sur une valeur de retour lorsque vous souhaitez que celle-ci soit déplacée et qu'elle ne serait pas déplacée de toute façon. C'est:
Considérant que ceci est assez délicat et que les mouvements sont généralement économiques, vous voudrez peut-être dire que, dans le code non-template, vous pouvez simplifier un peu cette opération. Utilisez std::move
Lorsque:
En suivant les règles simplifiées, vous sacrifiez certaines élisions de mouvement. Pour les types comme std::vector
Dont le déplacement est peu coûteux, vous ne le remarquerez probablement jamais (et si vous le remarquez, vous pouvez l'optimiser). Pour les types comme std::array
Dont le déplacement coûte cher, ou pour les modèles dans lesquels vous ne savez pas si les déménagements sont économiques ou non, vous en aurez probablement plus à vous inquiéter.
Le déménagement est inutile dans les deux cas. Dans le second cas, std::move
est superflu car vous retournez une variable locale par valeur et le compilateur comprendra que, puisque vous n'utiliserez plus cette variable locale, vous pouvez la déplacer plutôt que la copier.
Sur une valeur renvoyée, si l'expression return renvoie directement au nom d'une valeur locale (c'est-à-dire à une valeur x), la valeur std::move
. D'autre part, si l'expression de retour est pas l'identifiant, il ne sera pas déplacé automatiquement. Ainsi, par exemple, vous auriez besoin de l'explicite std::move
dans ce cas:
T foo(bool which) {
T a = ..., b = ...;
return std::move(which? a : b);
// alternatively: return which? std::move(a), std::move(b);
}
Lorsque vous retournez directement une variable locale nommée ou une expression temporaire, évitez le code explicite std::move
. Le compilateur doit (et le sera à l'avenir) se déplacer automatiquement dans ces cas, et ajouter std::move
pourrait affecter d’autres optimisations.
Il y a beaucoup de réponses sur le moment où il ne devrait pas être déplacé, mais la question est "quand devrait-il être déplacé?"
Voici un exemple artificiel du moment où il devrait être utilisé:
std::vector<int> append(std::vector<int>&& v, int x) {
v.Push_back(x);
return std::move(v);
}
c'est-à-dire que lorsque vous avez une fonction qui prend une référence rvalue, la modifie et en renvoie une copie. Maintenant, dans la pratique, cette conception est presque toujours meilleure:
std::vector<int> append(std::vector<int> v, int x) {
v.Push_back(x);
return v;
}
qui vous permet également de prendre des paramètres non rvalue.
En gros, si vous avez une référence rvalue dans une fonction que vous voulez retourner en déplaçant, vous devez appeler std::move
. Si vous avez une variable locale (que ce soit un paramètre ou non), vous la retournerez implicitement move
s (et ce déplacement implicite peut être éliminé, alors qu'un déplacement explicite ne peut pas l'être). Si vous avez une fonction ou une opération qui prend des variables locales et renvoie une référence à ladite variable locale, vous devez std::move
Pour que le déplacement se produise (à titre d'exemple, l'opérateur trinaire ?:
) .
Un compilateur C++ est libre d'utiliser std::move(foo)
:
foo
est en fin de vie, etstd::move
n'aura aucun effet sur la sémantique du code C++, à l'exception des effets sémantiques autorisés par la spécification C++.Cela dépend des capacités d'optimisation du compilateur C++ s'il est capable de calculer quelles transformations de f(foo); foo.~Foo();
à f(std::move(foo)); foo.~Foo();
sont rentables en termes de performances ou de consommation de mémoire, tout en y adhérant. les règles de spécification C++.
Conceptuellement pour parler, les compilateurs C++ de l'année 2017, tels que GCC 6.3.0, sont capables d'optimiser ce code:
Foo meh() {
Foo foo(args);
foo.method(xyz);
bar();
return foo;
}
dans ce code:
void meh(Foo *retval) {
new (retval) Foo(arg);
retval->method(xyz);
bar();
}
ce qui évite d'appeler le constructeur de copie et le destructeur de Foo
.
Les compilateurs C++ de l'année 2017, tels que GCC 6.3.0, sont impossible à optimiser ces codes:
Foo meh_value() {
Foo foo(args);
Foo retval(foo);
return retval;
}
Foo meh_pointer() {
Foo *foo = get_foo();
Foo retval(*foo);
delete foo;
return retval;
}
dans ces codes:
Foo meh_value() {
Foo foo(args);
Foo retval(std::move(foo));
return retval;
}
Foo meh_pointer() {
Foo *foo = get_foo();
Foo retval(std::move(*foo));
delete foo;
return retval;
}
ce qui signifie qu'un programmeur de l'année 2017 doit spécifier explicitement ces optimisations.