Je ne comprends pas quand je dois utiliser std::move
et quand je dois laisser le compilateur optimiser ... par exemple:
using SerialBuffer = vector< unsigned char >;
// let compiler optimize it
SerialBuffer read( size_t size ) const
{
SerialBuffer buffer( size );
read( begin( buffer ), end( buffer ) );
// Return Value Optimization
return buffer;
}
// explicit move
SerialBuffer read( size_t size ) const
{
SerialBuffer buffer( size );
read( begin( buffer ), end( buffer ) );
return move( buffer );
}
Que devrais-je utiliser?
Utilisez exclusivement la première méthode:
Foo f()
{
Foo result;
mangle(result);
return result;
}
Ceci déjà permettra l'utilisation du constructeur de déménagement, s'il en existe un. En fait, une variable locale peut être liée à une référence rvalue dans une instruction return
précisément lorsque l'autorisation de copie est autorisée.
Votre deuxième version interdit activement la copie. La première version est universellement meilleure.
Toutes les valeurs de retour sont déjà moved
ou optimisées, il n’est donc pas nécessaire de les déplacer explicitement avec les valeurs de retour.
Les compilateurs sont autorisés à déplacer automatiquement la valeur de retour (pour optimiser la copie), et même à optimiser le déplacement!
Section 12.8 du projet de norme n3337 (C++ 11):
Lorsque certains critères sont remplis, une implémentation est autorisée à omettre la construction de copie/déplacement d'un objet de classe, même si le constructeur et/ou le destructeur de copie/déplacement de l'objet ont des effets secondaires. Dans de tels cas, l’implémentation traite la source et la cible de l’opération de copie/déplacement omise comme simplement deux manières différentes de faire référence au même objet, et la destruction de cet objet se produit au plus tard des moments où les deux objets auraient été détruit sans l'optimisation.Cette élision d'opérations de copie/déplacement, appelée copie d'élision , est autorisée dans les cas suivants (pouvant être combinés pour éliminer plusieurs copies ):
[...]
Exemple :
class Thing { public: Thing(); ~Thing(); Thing(const Thing&); }; Thing f() { Thing t; return t; } Thing t2 = f();
Ici, les critères d'élision peuvent être combinés pour éliminer deux appels au constructeur de copie de la classe
Thing
: la copie de l'objet automatique localt
dans l'objet temporaire pour la valeur de retour de la fonctionf()
et la copie de cet objet temporaire dans l'objett2
. En réalité, la construction de l’objet localt
peut être considérée comme initialisant directement l’objet globalt2
, et la destruction de cet objet aura lieu à la sortie du programme. L'ajout d'un constructeur de déplacement àThing
a le même effet, mais c'est la construction de déplacement de l'objet temporaire àt2
qui est supprimée. - fin exemple ]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 exécuté comme si l'objet était désigné par une valeur. Si la résolution de surcharge échoue ou si le type du premier paramètre du constructeur sélectionné n’est pas une référence à la valeur rvalue du type de l’objet (éventuellement qualifié cv), la résolution de surcharge est à nouveau exécutée, en considérant l’objet comme une valeur.
C'est assez simple.
return buffer;
Si vous faites cela, alors NRVO aura lieu ou ne se produira pas. Si cela ne se produit pas, alors buffer
sera déplacé de.
return std::move( buffer );
Si vous faites cela, alors NVRO ne sera pas, et buffer
sera déplacé de.
Il n’ya donc rien à gagner à utiliser std::move
ici, et beaucoup à perdre.
Il y a une exception à cette règle:
Buffer read(Buffer&& buffer) {
//...
return std::move( buffer );
}
Si buffer
est une référence de valeur, vous devez alors utiliser std::move
. En effet, les références ne sont pas éligibles pour NRVO, donc, sans std::move
, il en résulterait une copie d'une valeur lvalue.
Il ne s'agit que d'une instance de la règle "toujours move
références de valeur et forward
références universelles", qui prime sur la règle "jamais move
une valeur de retour".
Si vous retournez une variable locale, n'utilisez pas move()
. Cela permettra au compilateur d'utiliser NRVO et, à défaut, il sera toujours autorisé à effectuer un déplacement (les variables locales deviennent des valeurs R dans une instruction return
). L'utilisation de move()
dans ce contexte inhiberait simplement NRVO et obligerait le compilateur à utiliser un déplacement (ou une copie si le déplacement n'est pas disponible). Si vous renvoyez une valeur autre qu'une variable locale, NRVO n'est de toute façon pas une option et vous devez utiliser move()
si (et seulement si) vous avez l'intention de voler l'objet.