c ++ 11 Optimisation de la valeur de retour ou déplacement?

Je ne comprends pas quand je devrais utiliser std::move et quand je devrais laisser le compilateur optimiser … par exemple:

 using SerialBuffer = vector; // let comstackr 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 dois-je utiliser?

Utilisez exclusivement la première méthode:

 Foo f() { Foo result; mangle(result); return result; } 

Cela permettra déjà l’utilisation du constructeur de déplacement, s’il en existe un. En fait, une variable locale peut se lier à une référence rvalue dans une déclaration de return précisément lorsque l’élimination de la copie est autorisée.

Votre deuxième version interdit activement l’élision de 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 mouvement!

Section 12.8 du brouillon standard n3337 (C ++ 11):

Lorsque certains critères sont remplis, une implémentation est autorisée à omettre la construction de copier / déplacer d’un object de classe, même si le constructeur de copie / déplacement et / ou le destructeur de l’object 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 simplement comme deux manières différentes de faire référence au même object, et la destruction de cet object se produit à la fin des deux objects. détruit sans l’optimisation. Cette élimination des opérations de copie / déplacement, appelée élision de copie , est autorisée dans les circonstances suivantes (qui peuvent être combinées pour éliminer les copies multiples):

[…]

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’object automatique local t dans l’object temporaire pour la valeur de retour de la fonction f() et la copie de cet object temporaire dans l’object t2 . En effet, la construction de l’object local t peut être vue comme l’initialisation directe de l’object global t2 , et la destruction de cet object se produira à la sortie du programme. Ajouter un constructeur de déplacement à Thing a le même effet, mais c’est la construction de déplacement de l’object temporaire à t2 qui est élidée. – exemple de fin ]

Lorsque les critères d’élision d’une opération de copie sont remplis ou sont satisfaits, à l’exception du fait que l’object source est un paramètre de fonction et que l’object à copier est désigné par une lvalue, la résolution de surcharge pour sélectionner le constructeur de la copie est d’abord effectué comme si l’object était désigné par une valeur. Si la résolution de la surcharge échoue ou si le type du premier paramètre du constructeur sélectionné n’est pas une référence à un type d’object (éventuellement qualifié cv), la résolution de la surcharge est à nouveau effectuée, en considérant l’object comme une valeur.

C’est assez simple

return buffer;

Si vous faites cela, alors soit NRVO se produira ou il ne le fera pas. Si cela ne se produit pas, le buffer sera déplacé de.

return std::move( buffer );

Si vous faites cela, NVRO ne se produira pas et le buffer sera déplacé de.

Il n’y a donc rien à gagner en utilisant std::move here 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, alors vous devriez utiliser std::move . C’est parce que les références ne sont pas éligibles pour NRVO, donc sans std::move cela se traduirait par une copie d’une lvalue.

Ceci est juste une instance de la règle “toujours move références rvalue et forward références universelles forward “, qui a la priorité sur la règle “ne 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, le compilateur sera toujours autorisé à effectuer un déplacement (les variables locales deviennent des valeurs R dans une déclaration de return ). L’utilisation de move() dans ce contexte inhiberait simplement NRVO et forcerait le compilateur à utiliser un mouvement (ou une copie si move n’est pas disponible). Si vous retournez quelque chose d’autre qu’une variable locale, NRVO n’est pas une option de toute façon et vous devriez utiliser move() si (et seulement si) vous avez l’intention de voler l’object.