Was sind Copy Elision und Return Value Optimization?

Lesezeit: 11 Minuten

Was sind Copy Elision und Return Value Optimization
Luchian Grigore

Was ist Copy Elision? Was ist (benannte) Rückgabewertoptimierung? Was bedeuten sie?

In welchen Situationen können sie auftreten? Was sind Einschränkungen?

  • Wenn Sie auf diese Frage verwiesen wurden, suchen Sie wahrscheinlich nach die Einleitung.
  • Eine technische Übersicht finden Sie unter die Standardreferenz.
  • Sehen häufige Fälle Hier.

  • Das Weglassen von Kopien ist eine Möglichkeit, es zu betrachten; Objektentfernung oder Objektverschmelzung (oder Verwirrung) ist eine andere Sichtweise.

    – Neugieriger

    25. August 2015 um 23:58 Uhr

  • ich habe das gefunden Verknüpfung hilfreich.

    – subtiler Sucher

    2. März 2020 um 8:30 Uhr

Was sind Copy Elision und Return Value Optimization
Luchian Grigore

Einführung

Für einen technischen Überblick – fahren Sie mit dieser Antwort fort.

Für häufige Fälle, in denen Kopien eliminiert werden, fahren Sie mit dieser Antwort fort.

Copy Elision ist eine Optimierung, die von den meisten Compilern implementiert wird, um zusätzliche (möglicherweise teure) Kopien in bestimmten Situationen zu verhindern. Es macht die Rückgabe nach Wert oder die Weitergabe nach Wert in der Praxis möglich (es gelten Einschränkungen).

Es ist die einzige Form der Optimierung, die (ha!) die Als-ob-Regel umgeht – Copy Elision kann auch angewendet werden, wenn das Kopieren/Verschieben des Objekts Seiteneffekte hat.

Das folgende Beispiel entnommen aus 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();
}

Je nach Compiler & Einstellungen die folgenden Ausgaben sind alle gültig:

Hallo Welt!
Es wurde eine Kopie angefertigt.
Es wurde eine Kopie angefertigt.


Hallo Welt!
Es wurde eine Kopie angefertigt.


Hallo Welt!

Dies bedeutet auch, dass weniger Objekte erstellt werden können, sodass Sie sich auch nicht darauf verlassen können, dass eine bestimmte Anzahl von Destruktoren aufgerufen wird. Sie sollten keine kritische Logik in Copy/Move-Konstruktoren oder Destruktoren haben, da Sie sich nicht darauf verlassen können, dass sie aufgerufen werden.

Wenn ein Aufruf eines Kopier- oder Verschiebekonstruktors eliminiert wird, muss dieser Konstruktor noch vorhanden und zugänglich sein. Dadurch wird sichergestellt, dass die Kopierelision kein Kopieren von Objekten zulässt, die normalerweise nicht kopierbar sind, zB weil sie einen privaten oder gelöschten Kopier-/Verschiebekonstruktor haben.

C++17: Ab C++17 ist Copy Elision garantiert, wenn ein Objekt direkt zurückgegeben wird:

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
}

  • Können Sie bitte erklären, wann die 2. Ausgabe erfolgt und wann die 3.?

    – zhangxaochen

    19. Juni 2014 um 14:00 Uhr


  • @zhangxaochen wann und wie der Compiler entscheidet, auf diese Weise zu optimieren.

    – Luchian Grigore

    19. Juni 2014 um 15:11 Uhr

  • @zhangxaochen, 1. Ausgabe: Kopie 1 stammt von der Rückkehr zu einem Temp und Kopie 2 von Temp zu Obj; 2. ist, wenn einer der oben genannten optimiert ist, wird wahrscheinlich die reutnr-Kopie entfernt; die thris sind beide elided

    – Sieger

    7. November 2014 um 16:06 Uhr

  • Hmm, aber meiner Meinung nach MUSS das ein Feature sein, auf das wir uns verlassen können. Denn wenn wir das nicht können, würde es die Art und Weise, wie wir unsere Funktionen in modernem C++ implementieren (RVO vs. std::move), stark beeinträchtigen. Als ich mir einige der Videos der CppCon 2014 ansah, hatte ich wirklich den Eindruck, dass alle modernen Compiler immer RVO machen. Außerdem habe ich irgendwo gelesen, dass die Compiler auch ohne irgendwelche Optimierungen darauf anwenden. Aber sicher bin ich mir da natürlich nicht. Deshalb frage ich.

    – j00hi

    5. Februar 2015 um 8:02 Uhr

  • @j00hi: Schreiben Sie niemals move in eine return-Anweisung – wenn rvo nicht angewendet wird, wird der Rückgabewert standardmäßig sowieso verschoben.

    – MikeMB

    10. März 2015 um 1:32 Uhr

Was sind Copy Elision und Return Value Optimization
Luchian Grigore

Standardreferenz

Für eine weniger technische Ansicht und Einführung – fahren Sie mit dieser Antwort fort.

Für häufige Fälle, in denen Kopien eliminiert werden, fahren Sie mit dieser Antwort fort.

Elision kopieren ist in der Norm definiert in:

12.8 Kopieren und Verschieben von Klassenobjekten [class.copy]

als

31) Wenn bestimmte Kriterien erfüllt sind, darf eine Implementierung die Copy/Move-Konstruktion eines Klassenobjekts weglassen, selbst wenn der Copy/Move-Konstruktor und/oder -Destruktor für das Objekt Seiteneffekte haben. In solchen Fällen behandelt die Implementierung die Quelle und das Ziel des ausgelassenen Kopier-/Verschiebevorgangs einfach als zwei verschiedene Arten, auf dasselbe Objekt zu verweisen, und die Zerstörung dieses Objekts erfolgt zu einem späteren Zeitpunkt, zu dem die beiden Objekte gewesen wären ohne die Optimierung zerstört.123 Diese Elision von Kopier-/Verschiebeoperationen, genannt Elision kopierenist unter den folgenden Umständen zulässig (die kombiniert werden können, um mehrere Kopien zu entfernen):

— in einer return-Anweisung in einer Funktion mit einem Klassen-Rückgabetyp, wenn der Ausdruck der Name eines nicht flüchtigen automatischen Objekts (anders als ein Funktions- oder Catch-Klausel-Parameter) mit demselben cvunqualifizierten Typ wie der Funktions-Rückgabetyp ist, der Der Kopier-/Verschiebevorgang kann weggelassen werden, indem das automatische Objekt direkt in den Rückgabewert der Funktion eingebaut wird

— in einem Throw-Ausdruck, wenn der Operand der Name eines nicht flüchtigen automatischen Objekts ist (anders als eine Funktion oder ein Catch-Klausel-Parameter), dessen Gültigkeitsbereich nicht über das Ende des innersten einschließenden Try-Blocks hinausgeht (falls vorhanden eins), kann die Kopier-/Verschiebeoperation vom Operanden zum Ausnahmeobjekt (15.1) weggelassen werden, indem das automatische Objekt direkt in das Ausnahmeobjekt eingebaut wird

— Wenn ein temporäres Klassenobjekt, das nicht an eine Referenz (12.2) gebunden wurde, in ein Klassenobjekt mit dem gleichen cv-unqualifizierten Typ kopiert/verschoben würde, kann die Kopier-/Verschiebeoperation weggelassen werden, indem das temporäre Objekt direkt in die konstruiert wird Ziel des ausgelassenen Kopierens/Verschiebens

— wenn die Exception-Deklaration eines Exception-Handlers (Abschnitt 15) ein Objekt des gleichen Typs (mit Ausnahme der CV-Qualifizierung) wie das Exception-Objekt (15.1) deklariert, kann der Kopier-/Verschiebevorgang durch Behandlung der Exception-Deklaration weggelassen werden als Alias ​​für das Ausnahmeobjekt, wenn die Bedeutung des Programms bis auf die Ausführung von Konstruktoren und Destruktoren für das durch die Ausnahmedeklaration deklarierte Objekt unverändert bleibt.

123) Da nur ein Objekt statt zwei zerstört wird und ein Kopier-/Verschiebekonstruktor nicht ausgeführt wird, wird immer noch ein Objekt für jedes konstruierte zerstört.

Das angegebene Beispiel ist:

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

und erklärt:

Hier können die Elision-Kriterien kombiniert werden, um zwei Aufrufe des Kopierkonstruktors von class zu eliminieren Thing: das Kopieren des lokalen automatischen Objekts t in das temporäre Objekt für den Rückgabewert der Funktion f()
und das Kopieren dieses temporären Objekts in Objekt t2. Effektiv die Konstruktion des lokalen Objekts t
kann als direktes Initialisieren des globalen Objekts betrachtet werden t2, und die Zerstörung dieses Objekts erfolgt beim Programmende. Das Hinzufügen eines Move-Konstruktors zu Thing hat den gleichen Effekt, aber es ist die Move-Konstruktion vom temporären Objekt zu t2 das ist eliminiert.

  • Ist das aus dem C++17-Standard oder aus einer früheren Version?

    – Nils

    7. Mai 2019 um 12:29 Uhr

  • Warum kann der Funktionsparameter nicht für den Rückgabewert optimiert werden, wenn er vom gleichen Typ ist wie der Rückgabetyp der Funktion?

    – Sahil Singh

    20. Juni 2020 um 19:26 Uhr

  • Dies versucht zu beantworten – stackoverflow.com/questions/9444485/…

    – Sahil Singh

    20. Juni 2020 um 19:48 Uhr

  • Gibt es eine Art von Copy-Elision für primitive Typen? Wenn ich eine Funktion habe, die einen Rückgabewert (vielleicht einen Fehlercode) propagiert, gibt es dann eine Optimierung ähnlich wie bei Objekten?

    – WARhead

    3. August 2020 um 12:50 Uhr

Was sind Copy Elision und Return Value Optimization
Luchian Grigore

Gängige Formen der Kopierelision

Für einen technischen Überblick – fahren Sie mit dieser Antwort fort.

Für eine weniger technische Ansicht und Einführung – fahren Sie mit dieser Antwort fort.

Die (benannte) Rückgabewertoptimierung ist eine gängige Form der Kopierelision. Es bezieht sich auf die Situation, in der die Kopie eines Objekts, das als Wert von einer Methode zurückgegeben wird, entfernt wird. Das in der Norm aufgeführte Beispiel veranschaulicht dies namens Rückgabewertoptimierungda das Objekt benannt ist.

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

Regulär Renditeoptimierung tritt auf, wenn ein temporäres zurückgegeben wird:

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

Andere häufige Orte, an denen Kopien entfernt werden, sind, wenn a Temporär wird als Wert übergeben:

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

foo(Thing());

oder wenn ein Die Ausnahme wird ausgelöst und vom Wert abgefangen:

struct Thing{
  Thing();
  Thing(const Thing&);
};
 
void foo() {
  Thing c;
  throw c;
}
 
int main() {
  try {
    foo();
  }
  catch(Thing c) {  
  }             
}

Übliche Beschränkungen für das Entfernen von Kopien sind:

  • mehrere Rückkehrpunkte
  • Bedingte Initialisierung

Die meisten kommerziellen Compiler unterstützen Copy Elision & (N)RVO (abhängig von den Optimierungseinstellungen).

  • Ich wäre daran interessiert, die Aufzählungspunkte “Häufige Einschränkungen” nur ein wenig erklärt zu sehen … was macht diese einschränkenden Faktoren aus?

    – Telefontagger

    16. Januar 2013 um 17:54 Uhr

  • @phonetagger Ich habe mit dem msdn-Artikel verlinkt, hoffe, das klärt einiges.

    – Luchian Grigore

    16. Januar 2013 um 19:11 Uhr

Was sind Copy Elision und Return Value Optimization
Ajay yadav

Copy Elision ist eine Compiler-Optimierungstechnik, die unnötiges Kopieren/Verschieben von Objekten eliminiert.

Unter folgenden Umständen darf ein Compiler Kopier-/Verschiebeoperationen weglassen und daher den zugehörigen Konstruktor nicht aufrufen:

  1. NRVO (Named-Return-Value-Optimierung): Wenn eine Funktion einen Klassentyp nach Wert zurückgibt und der Ausdruck der Rückgabeanweisung der Name eines nichtflüchtigen Objekts mit automatischer Speicherdauer ist (was kein Funktionsparameter ist), dann würde das Kopieren/Verschieben von einem non -Optimierender Compiler kann weggelassen werden. Wenn dies der Fall ist, wird der zurückgegebene Wert direkt in dem Speicher konstruiert, in den der Rückgabewert der Funktion sonst verschoben oder kopiert würde.
  2. RVO (Return Value Optimierung): Wenn die Funktion ein namenloses temporäres Objekt zurückgibt, das von einem naiven Compiler in das Ziel verschoben oder kopiert würde, kann das Kopieren oder Verschieben gemäß 1. weggelassen werden.
#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());    //RVO, not NRVO 
    ABC xyz = "Stack Overflow";//RVO  
    return 0;  
}

**Output without -fno-elide-constructors**  
root@ajay-PC:/home/ajay/c++# ./a.out   
Constructor    
Constructor  
Constructor  
Constructor  
Destructor  
Destructor  
Destructor  
Destructor  

**Output with -fno-elide-constructors**  
root@ajay-PC:/home/ajay/c++# g++ -std=c++11 copy_elision.cpp -fno-elide-constructors    
root@ajay-PC:/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  

Selbst wenn eine Kopierentfernung stattfindet und der Kopier-/Verschiebekonstruktor nicht aufgerufen wird, muss er vorhanden und zugänglich sein (als ob überhaupt keine Optimierung stattgefunden hätte), sonst ist das Programm falsch formatiert.

Sie sollten ein solches Entfernen von Kopien nur an Stellen zulassen, an denen es das beobachtbare Verhalten Ihrer Software nicht beeinträchtigt. Copy Elision ist die einzige Form der Optimierung, die beobachtbare Nebeneffekte haben (dh eliminieren) darf. Beispiel:

#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  
root@ajay-PC:/home/ayadav# g++ -std=c++11 copy_elision.cpp  
root@ajay-PC:/home/ayadav# ./a.out   
0

Output with -fno-elide-constructors  
root@ajay-PC:/home/ayadav# g++ -std=c++11 copy_elision.cpp -fno-elide-constructors  
root@ajay-PC:/home/ayadav# ./a.out   
1

GCC bietet die -fno-elide-constructors Option zum Deaktivieren der Kopierentfernung. Wenn Sie ein mögliches Weglassen von Kopien vermeiden möchten, verwenden Sie -fno-elide-constructors.

Jetzt bieten fast alle Compiler das Entfernen von Kopien an, wenn die Optimierung aktiviert ist (und wenn keine andere Option zum Deaktivieren eingestellt ist).

Fazit

Bei jedem Wegfall einer Kopie entfallen ein Aufbau und eine passende Zerstörung der Kopie, wodurch CPU-Zeit gespart wird, und ein Objekt wird nicht erzeugt, wodurch Platz auf dem Stapelrahmen gespart wird.

Hier gebe ich ein weiteres Beispiel für das Weglassen von Kopien, dem ich anscheinend heute begegnet bin.

# include <iostream>


class Obj {
public:
  int var1;
  Obj(){
    std::cout<<"In   Obj()"<<"\n";
    var1 =2;
  };
  Obj(const Obj & org){
    std::cout<<"In   Obj(const Obj & org)"<<"\n";
    var1=org.var1+1;
  };
};

int  main(){

  {
    /*const*/ Obj Obj_instance1;  //const doesn't change anything
    Obj Obj_instance2;
    std::cout<<"assignment:"<<"\n";
    Obj_instance2=Obj(Obj(Obj(Obj(Obj_instance1))))   ;
    // in fact expected: 6, but got 3, because of 'copy elision'
    std::cout<<"Obj_instance2.var1:"<<Obj_instance2.var1<<"\n";
  }

}

Mit dem Ergebnis:

In   Obj()
In   Obj()
assignment:
In   Obj(const Obj & org)
Obj_instance2.var1:3

  • Das ist bereits in Luchians Antwort enthalten (temporäres Objekt, das als Wert übergeben wird).

    – Toby Speight

    23. Dezember 2020 um 13:45 Uhr

  • Das ist bereits in Luchians Antwort enthalten (temporäres Objekt, das als Wert übergeben wird).

    – Toby Speight

    23. Dezember 2020 um 13:45 Uhr

1003580cookie-checkWas sind Copy Elision und Return Value Optimization?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy