Quand std:: move doit-il être utilisé sur une valeur de retour de fonction? [dupliquer]
cette question a déjà une réponse ici:
dans ce cas
struct Foo {};
Foo meh() {
return std::move(Foo());
}
je suis assez sûr que le déplacement est inutile, parce que le nouveau Foo
sera un xvalue.
mais qu'est-ce que dans des cas comme celui-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 mouvement est nécessaire, je suppose?
6 réponses
dans le cas de return std::move(foo);
le move
est superflu en raison de 12.8/32:
lorsque les critères de décision d'une opération de copie sont respectés ou met sauf que l'objet source est un paramètre de fonction, et l'objet à copier est désigné par une lvalue, surcharge la résolution pour sélectionner le constructeur de la copie est d'abord effectuée comme si l'objet a été désigné par une valeur.
return foo;
est un cas de NRVO, pour copier élision est autorisée. foo
est une valeur. Ainsi, le constructeur sélectionné pour la "copie" de foo
à la valeur de retour de meh
doit être le constructeur de déplacement si l'un existe.
L'ajout de move
a toutefois un effet potentiel: il empêche l'élimination du mouvement, parce que return std::move(foo);
est et non admissible pour L'OAVN.
aussi loin que Je sais, 12.8 / 32 énonce les seulement conditions dans lesquelles une copie d'une valeur l Peut être remplacée par un déplacement. Le compilateur n'est pas autorisé en général à détecter qu'une valeur l n'est pas utilisée après la copie (en utilisant DFA, par exemple), et à effectuer le changement 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 alors la règle "as-si" s'applique.
ainsi, pour répondre à la question dans le titre, utiliser std::move
sur une valeur de retour quand vous voulez être déplacé et qu'il ne serait pas déplacé de toute façon. C'est-à-dire:
- vous voulez qu'il soit déplacé, et
- c'est une valeur, et
- il n'est pas admissible à la copie décision, et
- ce n'est pas le nom d'une fonction de valeur de paramètre.
Considérant que c'est assez laborieux et les déplacements sont habituellement pas cher, vous pourriez dire que dans le code non-template vous pouvez simplifier cela un peu. Utiliser std::move
quand:
- vous voulez qu'il soit déplacé, et
- c'est une valeur, et
- ça ne vous dérange pas de vous en inquiéter.
en suivant les règles simplifiées vous sacrifiez un peu d'élision de mouvement. Pour les types comme std::vector
qui sont bon marché pour se déplacer vous aurez probablement ne remarquez jamais (et si vous remarquez, vous pouvez optimiser). Pour les types comme std::array
qui sont coûteux à déplacer, ou pour les modèles où vous n'avez aucune idée si les mouvements sont bon marché ou non, vous êtes plus susceptibles d'être ennuyé s'en inquiéter.
le déménagement n'est pas nécessaire dans les deux cas. Dans le second cas, std::move
est superflu parce que vous retournez une variable locale en valeur, et le compilateur comprendra que puisque vous n'allez plus utiliser cette variable locale, elle peut être déplacée plutôt que copiée.
sur une valeur de retour, si l'expression de retour se réfère directement au nom d'une valeur l locale (C.-à-d. à ce point une valeur X) il n'y a pas besoin de la std::move
. D'autre part, si le retour de l'expression est pas l'identifiant, il ne sera pas déplacé automatiquement, par exemple, vous aurez 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, vous devrait éviter le std::move
explicite . Le compilateur doit (et se déplacera à l'avenir) se déplacer automatiquement dans ces cas, et l'ajout de 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 doit être utilisé:
std::vector<int> append(std::vector<int>&& v, int x) {
v.push_back(x);
return std::move(v);
}
ie, quand vous avez une fonction qui prend une référence rvalue, la modifie, puis renvoie une copie de celle-ci. 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 non-valeur paramètre.
en gros, si vous avez une référence rvalue dans une fonction que vous voulez retourner en vous déplaçant, vous devez appeler std::move
. Si vous avez une variable locale (que ce soit un paramètre ou non), renvoyant implicitement move
s (et ce mouvement implicite peut être effacé, tandis qu'un mouvement explicite ne peut pas). 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 obtenir le mouvement à se produisent (par exemple, l'opérateur trinary ?:
).
un compilateur C++ est libre d'utiliser std::move(foo)
:
- s'il est connu que
foo
est à la fin de sa vie, et - l'utilisation implicite de
std::move
n'aura aucun effet sur la sémantique du code c++ autre que les 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 les transformations de f(foo); foo.~Foo();
à f(std::move(foo)); foo.~Foo();
sont rentables en termes de performance ou en termes de consommation de mémoire, tout en respectant les règles de spécification C++.
conceptuellement parlant, année-2017 compilateurs C++, tels que GCC 6.3.0, sont capable 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();
}
qui évite d'appeler le copy-constructor et le destructeur de Foo
.
de l'Année 2017 compilateurs C++, comme GCC 6.3.0, sont impossible d'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 de telles optimisations.
std::move
est totalement inutile en revenant d'une fonction, et pénètre vraiment dans le domaine de vous -- le programmeur -- essayant de garder des choses que vous devriez laisser au compilateur.
Ce qui se passe quand vous std::move
quelque chose d'une fonction qui n'est pas une variable locale à cette fonction? Vous pouvez dire que vous n'écrirez jamais de code comme ça, mais que se passe-t-il si vous écrivez du code qui est tout simplement parfait, et puis le reformater et de manière inconsciente ne changez le std::move
. Tu vas t'amuser à traquer ce bug.
le compilateur, d'un autre côté, est généralement incapable de faire ce genre d'erreurs.
Aussi: il est Important de noter que le retour d'une variable locale d'une fonction pas nécessairement créer une rvalue ou l'utilisation d'une sémantique de déplacement.