Qu'est-ce que l'optimisation de la valeur de copie et de retour?

Qu'est-ce que copy elision? Qu'est-ce que l'optimisation de la valeur de retour (nommée)? Que font-ils impliquent?

dans quelles situations peuvent-elles se produire? Quelles sont les limites?

272
demandé sur Community 2012-10-18 15:03:03
la source

4 ответов

Introduction

Pour une vue d'ensemble technique passer à cette réponse .

pour les cas courants où il y a Copie - passez à cette réponse .

Copy elision est une optimisation implémentée par la plupart des compilateurs pour éviter les copies supplémentaires (potentiellement coûteuses) dans certaines situations. Il rend le retour en valeur ou le passage en valeur réalisable dans la pratique (des restrictions s'appliquent).

C'est la seule forme d'optimisation qui Elide (ha!) la règle as-if - copy elision peut être appliquée même si copier/déplacer l'objet a des effets secondaires .

l'exemple suivant tiré de Wikipedia :

struct C {
  C() {}
  C(const C&) { std::cout << "A copy was made.\n"; }
};

C f() {
  return C();
}

int main() {
  std::cout << "Hello World!\n";
  C obj = f();
}

selon le compilateur et les paramètres, les sorties suivantes sont toutes valides :

Hello Monde!

Une copie a été faite.

Une copie a été faite.


Bonjour Tout Le Monde!

Une copie a été faite.


Bonjour Tout Le Monde!

cela signifie également moins d'objets peuvent être créés, de sorte que vous ne pouvez pas non plus compter sur un nombre précis de destructeurs appelés. Vous ne devriez pas avoir de logique critique à l'intérieur de copy/move-constructors ou destructeurs, car vous ne pouvez pas compter sur eux étant appelé.

si un appel à un constructeur de copie ou de déplacement est supprimé, ce constructeur doit toujours exister et doit être accessible. Cela garantit que copy elision ne permet pas de copier des objets qui ne sont pas normalement copiables, par exemple parce qu'ils ont un constructeur de copie/déplacement privé ou supprimé.

C++17 : à partir de C++17, L'élision de copie est garantie lorsqu'un objet est retourné directement:

struct C {
  C() {}
  C(const C&) { std::cout << "A copy was made.\n"; }
};

C f() {
  return C(); //Definitely performs copy elision
}
C g() {
    C c;
    return c; //Maybe performs copy elision
}

int main() {
  std::cout << "Hello World!\n";
  C obj = f(); //Copy constructor isn't called
}
175
répondu Luchian Grigore 2018-05-31 06:25:35
la source

référence Standard

Pour moins de vue technique et introduction - passer à cette réponse .

Pour le cas où la copie élision se produit - passer à cette réponse .

Copie élision est défini dans la norme:

de 12,8 la Copie et le déplacement des objets de la classe [classe.copy]

comme

31) lorsque certains critères sont respectés, une mise en œuvre est autorisée à omettre la construction de copie/déménagement d'une classe objet, même si le constructeur de copie/déplacement et/ou destructeur pour 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 simplement comme deux différentes manières de se référer au même objet, et la destruction de cet objet se produit au plus tard à la fois lorsque les deux les objets ont été détruits sans l'optimisation. 123 Cette élision de copier/déplacer opérations, appelées copie élision , est autorisé dans les circonstances suivantes (qui peuvent être combinées pour supprimer les copies multiples):

- dans une déclaration de retour dans une fonction avec un type de retour de classe, lorsque l'expression est le nom d'un objet automatique non volatil (autre qu'une fonction ou un paramètre de clause de récupération)) avec la même cvunqualified tapez comme le type de retour de la fonction, l'opération de copie/déplacement peut être omise en construisant l'objet automatique directement dans la valeur de retour de la fonction

- dans une expression de lancer, lorsque l'opérande est le nom d'un objet automatique non volatil (autre qu'un fonction ou paramètre catch-clause) dont le champ d'application ne s'étend pas au-delà de la fin de la plus proche le bloc d'essai (s'il y en a un), l'opération copier/déplacer à partir du opérande à l'exception l'objet (15.1) peut être omis en construisant l'objet automatique directement dans l'objet d'exception

- lorsqu'un objet de classe temporaire qui n'est pas lié à un renvoi (12.2) serait copié / déplacé pour un objet de classe avec le même type cv-unqualified, l'opération copier/déplacer peut être omise par construire l'objet temporaire directement dans la cible de la copie/déplacement omise

- lorsque le exception-déclaration d'un gestionnaire d'exception (article 15) déclare un objet de même type (sauf pour cv-qualification) en tant qu'objet d'exception (15.1), l'opération copie / déplacement peut être omise en traitant la déclaration d'exception comme un alias pour l'objet d'exception si le sens du programme sera inchangé, sauf pour l'exécution de constructeurs et destructeurs pour l'objet déclaré par l'exception-déclaration.

123) parce que un seul objet est détruit au lieu de deux, et un copier/déplacer constructeur n'est pas exécuté, il est toujours une objet détruit pour chacun construit.

l'exemple donné est:

class Thing {
public:
  Thing();
  ~Thing();
  Thing(const Thing&);
};
Thing f() {
  Thing t;
  return t;
}
Thing t2 = f();

et expliqué:

ici, les critères d'elision peuvent être combinés pour éliminer deux appels au constructeur de copie de la classe Thing : la copie du ou des locaux automatique objet t dans l'objet temporaire pour la valeur de retour de la fonction f() et la copie de l'objet temporaire en objet t2 . Effectivement, la construction de l'objet local t peut être considéré comme initialisant directement l'objet global t2 , et la destruction de cet objet se produira au programme sortie. Ajouter un constructeur de mouvement à la chose a le même effet, mais c'est la construction de mouvement de la objet temporaire de t2 c'est élidée.

78
répondu Luchian Grigore 2017-05-23 15:03:07
la source

les formes les plus Courantes de la copie élision

Pour une vue d'ensemble technique passer à cette réponse .

Pour moins de vue technique et introduction - passer à cette réponse .

(nommé) l'optimisation de la valeur de retour est une forme courante d'élision de copie. Il se réfère à la situation où un objet retourné par la valeur d'une méthode a sa copie elided. L'exemple énoncées dans la norme illustre nommé optimisation de la valeur de retour , puisque l'objet est nommé.

class Thing {
public:
  Thing();
  ~Thing();
  Thing(const Thing&);
};
Thing f() {
  Thing t;
  return t;
}
Thing t2 = f();

Régulier valeur de retour d'optimisation se produit lorsqu'un temporaire est retourné:

class Thing {
public:
  Thing();
  ~Thing();
  Thing(const Thing&);
};
Thing f() {
  return Thing();
}
Thing t2 = f();

les autres endroits communs où la copie a lieu sont quand un temporaire est passé par la valeur :

class Thing {
public:
  Thing();
  ~Thing();
  Thing(const Thing&);
};
void foo(Thing t);

foo(Thing());

ou lorsqu'une exception est lancée et valeur :

struct Thing{
  Thing();
  Thing(const Thing&);
};

void foo() {
  Thing c;
  throw c;
}

int main() {
  try {
    foo();
  }
  catch(Thing c) {  
  }             
}

les limitations courantes de la copie sont:

  • plusieurs points de retour
  • initialisation conditionnelle

la plupart des compilateurs commerciaux prennent en charge la copie elision & (N)RVO (selon les paramètres d'optimisation).

70
répondu Luchian Grigore 2017-05-23 15:34:37
la source

L'élision de copie est une technique d'optimisation de compilateur qui élimine la copie/déplacement inutile d'objets.

dans les circonstances suivantes, un compilateur est autorisé à omettre les opérations de copie/déplacement et donc à ne pas appeler le constructeur associé:

  1. NRVO (Named Return Value Optimization) : si une fonction renvoie un type de classe en fonction de la valeur et que l'expression de l'instruction return est le nom d'un objet non volatile avec durée de stockage automatique (qui n'est pas un paramètre de fonction), puis la copie/déplacement qui serait effectuée par un compilateur non-optimisant peut être omis. Si c'est le cas, la valeur retournée est construite directement dans la mémoire vers laquelle la valeur de retour de la fonction serait autrement déplacée ou copiée.
  2. RVO (Return Value Optimization) : si la fonction renvoie un objet temporaire sans nom qui serait déplacé ou copié dans le destination par un compilateur naïf, la copie ou le déplacement peuvent être omis selon 1.
#include <iostream>  
using namespace std;

class ABC  
{  
public:   
    const char *a;  
    ABC()  
     { cout<<"Constructor"<<endl; }  
    ABC(const char *ptr)  
     { cout<<"Constructor"<<endl; }  
    ABC(ABC  &obj)  
     { cout<<"copy constructor"<<endl;}  
    ABC(ABC&& obj)  
    { cout<<"Move constructor"<<endl; }  
    ~ABC()  
    { cout<<"Destructor"<<endl; }  
};

ABC fun123()  
{ ABC obj; return obj; }  

ABC xyz123()  
{  return ABC(); }  

int main()  
{  
    ABC abc;  
    ABC obj1(fun123());//NRVO  
    ABC obj2(xyz123());//NRVO  
    ABC xyz = "Stack Overflow";//RVO  
    return 0;  
}

**Output without -fno-elide-constructors**  
[email protected]:/home/ajay/c++# ./a.out   
Constructor    
Constructor  
Constructor  
Constructor  
Destructor  
Destructor  
Destructor  
Destructor  

**Output with -fno-elide-constructors**  
[email protected]:/home/ajay/c++# g++ -std=c++11 copy_elision.cpp -fno-elide-constructors    
[email protected]:/home/ajay/c++# ./a.out   
Constructor  
Constructor  
Move constructor  
Destructor  
Move constructor  
Destructor  
Constructor  
Move constructor  
Destructor  
Move constructor  
Destructor  
Constructor  
Move constructor  
Destructor  
Destructor  
Destructor  
Destructor  
Destructor  

même lorsque l'élision de copie a lieu et que le copy-/move-constructor n'est pas appelé, il doit être présent et accessible (comme si aucune optimisation n'avait eu lieu), sinon le programme est mal formé.

vous devez permettre une telle décision de copie seulement dans les endroits où elle n'affectera pas le comportement observable de votre logiciel. Copie élision est la seule forme de optimisation autorisée à faire (c'est à dire éluder) observables effets secondaires. Exemple:

#include <iostream>     
int n = 0;    
class ABC     
{  public:  
 ABC(int) {}    
 ABC(const ABC& a) { ++n; } // the copy constructor has a visible side effect    
};                     // it modifies an object with static storage duration    

int main()   
{  
  ABC c1(21); // direct-initialization, calls C::C(42)  
  ABC c2 = ABC(21); // copy-initialization, calls C::C( C(42) )  

  std::cout << n << std::endl; // prints 0 if the copy was elided, 1 otherwise
  return 0;  
}

Output without -fno-elide-constructors  
[email protected]:/home/ayadav# g++ -std=c++11 copy_elision.cpp  
[email protected]:/home/ayadav# ./a.out   
0

Output with -fno-elide-constructors  
[email protected]:/home/ayadav# g++ -std=c++11 copy_elision.cpp -fno-elide-constructors  
[email protected]:/home/ayadav# ./a.out   
1

GCC fournit l'option -fno-elide-constructors pour désactiver la copie elision. Si vous voulez éviter toute possibilité de copie, utilisez -fno-elide-constructors .

maintenant presque tous les compilateurs fournissent une sélection de copie lorsque l'optimisation est activée (et si aucune autre option n'est définie pour la désactiver).

Conclusion

avec chaque copie la construction et une destruction correspondante de la copie sont omises, ce qui permet D'économiser du temps CPU, et un objet n'est pas créé, ce qui permet d'économiser de l'espace sur le cadre de la pile.

37
répondu Ajay yadav 2016-06-27 23:37:11
la source