web-dev-qa-db-fra.com

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

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?

160
elvis.dukaj

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.

103
Kerrek SB

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 local t dans l'objet temporaire pour la valeur de retour de la fonction f() et la copie de cet objet temporaire dans l'objet t2. En réalité, la construction de l’objet local t peut être considérée comme initialisant directement l’objet global t2, 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.

114
Jamin Grey

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".

46
Oktalist

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.

24
Adam H. Peterson