Wie funktioniert die garantierte Entfernung von Kopien?

Lesezeit: 7 Minuten

Wie funktioniert die garantierte Entfernung von Kopien
Jotik

Beim ISO C++ Standards Meeting 2016 in Oulu wurde ein Vorschlag genannt Garantierter Kopierschutz durch vereinfachte Wertekategorien wurde vom Standards Committee in C++17 gewählt.

Wie genau funktioniert die garantierte Entfernung von Kopien? Deckt es einige Fälle ab, in denen das Entfernen von Kopien bereits erlaubt war, oder sind Codeänderungen erforderlich, um das Entfernen von Kopien zu gewährleisten?

1647222609 829 Wie funktioniert die garantierte Entfernung von Kopien
Nicol Bola

Das Löschen von Kopien war unter einer Reihe von Umständen zulässig. Aber selbst wenn es erlaubt wäre, musste der Code immer noch so funktionieren können, als ob die Kopie nicht entfernt wäre. Es musste nämlich einen zugänglichen Kopier- und/oder Bewegungskonstruktor geben.

Das garantierte Entfernen von Kopien definiert eine Reihe von C++-Konzepten neu, sodass bestimmte Umstände, in denen Kopien/Verschiebungen eliminiert werden könnten, nicht wirklich eine Kopie/Verschiebung provozieren überhaupt. Der Compiler entfernt keine Kopie; Der Standard besagt, dass ein solches Kopieren niemals passieren könnte.

Betrachten Sie diese Funktion:

T Func() {return T();}

Unter nicht garantierten Regeln zum Entfernen von Kopien wird dadurch ein temporäres erstellt und dann von diesem temporären Objekt in den Rückgabewert der Funktion verschoben. Diese Bewegungsoperation dürfen ausgeschlossen werden, aber T muss immer noch einen zugänglichen Bewegungskonstruktor haben, auch wenn er nie verwendet wird.

Ähnlich:

T t = Func();

Dies ist die Kopierinitialisierung von t. Dadurch wird die Kopie initialisiert t mit dem Rückgabewert von Func. Aber, T muss immer noch einen Move-Konstruktor haben, auch wenn er nicht aufgerufen wird.

Garantierter Kopierschutz definiert die Bedeutung eines Prvalue-Ausdrucks neu. Prvalues ​​sind vor C++17 temporäre Objekte. In C++17 ist ein Prvalue-Ausdruck lediglich etwas, das kann materialisieren ein Provisorium, aber es ist noch kein Provisorium.

Wenn Sie einen Prvalue verwenden, um ein Objekt vom Typ des Prvalue zu initialisieren, wird kein temporäres Objekt materialisiert. Wenn Sie das tun return T();, initialisiert dies den Rückgabewert der Funktion über einen Prvalue. Da diese Funktion zurückkehrt T, wird kein Temporär erstellt; Die Initialisierung des Prvalue initialisiert einfach direkt den Rückgabewert.

Da es sich bei dem Rückgabewert um einen Prvalue handelt, ist dies zu verstehen kein Objekt noch. Es ist lediglich ein Initialisierer für ein Objekt, genau wie T() ist.

Wenn Sie das tun T t = Func();initialisiert der Prvalue des Rückgabewerts direkt das Objekt t; Es gibt keine Phase “temporär erstellen und kopieren/verschieben”. Seit Func()Der Rückgabewert von ist ein prvalue-Äquivalent zu T(), t wird direkt durch initialisiert T()genau so, als hättest du es getan T t = T().

Wenn ein Prvalue auf andere Weise verwendet wird, materialisiert der Prvalue ein temporäres Objekt, das in diesem Ausdruck verwendet wird (oder verworfen wird, wenn es keinen Ausdruck gibt). Also, wenn Sie es getan haben const T &rt = Func();würde der prvalue eine temporäre (mithilfe von T() als Initialisierer), dessen Referenz gespeichert werden würde rtzusammen mit den üblichen Dingen zur vorübergehenden Verlängerung der Lebensdauer.

Eine Sache, die Ihnen die garantierte Elision erlaubt, ist die Rückgabe von unbeweglichen Objekten. Zum Beispiel, lock_guard kann nicht kopiert oder verschoben werden, sodass Sie keine Funktion haben könnten, die es als Wert zurückgibt. Aber mit garantierter Kopierentfernung ist das möglich.

Garantierte Elision funktioniert auch mit direkter Initialisierung:

new T(FactoryFunction());

Wenn FactoryFunction kehrt zurück T nach Wert kopiert dieser Ausdruck den Rückgabewert nicht in den zugewiesenen Speicher. Stattdessen wird Speicher zugewiesen und verwendet den zugewiesenen Speicher als Rückgabewertspeicher für den Funktionsaufruf direkt.

Factory-Funktionen, die als Wert zurückgeben, können also Heap-zugewiesenen Speicher direkt initialisieren, ohne es überhaupt zu wissen. Solange diese funktionieren im Inneren befolgen Sie natürlich die Regeln der garantierten Kopienelision. Sie müssen einen Prvalue vom Typ zurückgeben T.

Das geht natürlich auch:

new auto(FactoryFunction());

Falls Sie nicht gerne Typennamen schreiben.


Es ist wichtig zu erkennen, dass die oben genannten Garantien nur für Prvalues ​​funktionieren. Das heißt, Sie erhalten bei Rücksendung keine Garantie genannt Variable:

T Func()
{
   T t = ...;
   ...
   return t;
}

In diesem Fall, t muss noch einen zugänglichen Copy/Move-Konstruktor haben. Ja, der Compiler kann das Kopieren/Verschieben wegoptimieren. Aber der Compiler muss immer noch die Existenz eines zugänglichen Copy/Move-Konstruktors überprüfen.

Für die benannte Rückgabewertoptimierung (NRVO) ändert sich also nichts.

  • @BenVoigt: Das Einfügen von nicht trivial kopierbaren benutzerdefinierten Typen in Register ist keine praktikable Sache, die ein ABI tun kann, unabhängig davon, ob Elision verfügbar ist oder nicht.

    – Nicol Bolas

    26. Juni 2016 um 22:49 Uhr

  • Jetzt, da die Regeln öffentlich sind, kann es sich lohnen, dies mit dem Konzept “prvalues ​​are initializations” zu aktualisieren.

    – Johannes Schaub – litb

    12. September 2016 um 19:36 Uhr

  • @JohannesSchaub-litb: Es ist nur “mehrdeutig”, wenn Sie zu viel über die Einzelheiten des C++-Standards wissen. 99 % der C++-Community wissen, worauf sich “garantiertes Entfernen von Kopien” bezieht. Das eigentliche Papier, das die Funktion vorschlägt, ist sogar betitelt “Garantierte Kopienentfernung”. Das Hinzufügen von „durch vereinfachte Wertkategorien“ macht es nur verwirrend und für Benutzer schwer verständlich. Es ist auch eine falsche Bezeichnung, da diese Regeln die Regeln um Wertkategorien nicht wirklich “vereinfachen”. Ob es Ihnen gefällt oder nicht, der Begriff “garantiertes Entfernen von Kopien” bezieht sich auf diese Funktion und auf nichts anderes.

    – Nicol Bolas

    28. Mai 2017 um 13:41 Uhr

  • Ich möchte in der Lage sein, einen Prvalue aufzuheben und herumzutragen. Ich denke, das ist nur ein (One-Shot) std::function<T()> Ja wirklich.

    – Yakk – Adam Nevraumont

    30. Mai 2017 um 17:17 Uhr

  • @LukasSalich: Das ist eine C++11-Frage. Diese Antwort bezieht sich auf ein C++17-Feature.

    – Nicol Bolas

    26. Juni 2019 um 19:04 Uhr

Ich denke, dass Details zur Elision von Kopien hier gut geteilt wurden. Allerdings habe ich diesen Artikel gefunden: https://jonasdevlieghere.com/guaranteed-copy-elision Dies bezieht sich auf das garantierte Entfernen von Kopien in C++17 im Fall der Rückgabewertoptimierung.

Es bezieht sich auch darauf, wie man mit der gcc-Option: -fno-elide-constructors die Kopierelision deaktivieren und sehen kann, dass anstelle des direkten Aufrufs des Konstruktors am Ziel 2 Kopierkonstruktoren (oder Verschiebungskonstruktoren in c ++ 11 ) und ihre entsprechenden Destruktoren aufgerufen werden. Das folgende Beispiel zeigt beide Fälle:

#include <iostream>
using namespace std;
class Foo {
public:
    Foo() {cout << "Foo constructed" << endl; }
    Foo(const Foo& foo) {cout << "Foo copy constructed" << endl;}
    Foo(const Foo&& foo) {cout << "Foo move constructed" << endl;}
    ~Foo() {cout << "Foo destructed" << endl;}
};

Foo fReturnValueOptimization() {
    cout << "Running: fReturnValueOptimization" << endl;
    return Foo();
}

Foo fNamedReturnValueOptimization() {
    cout << "Running: fNamedReturnValueOptimization" << endl;
    Foo foo;
    return foo;
}

int main() {
    Foo foo1 = fReturnValueOptimization();
    Foo foo2 = fNamedReturnValueOptimization();
}
vinegupt@bhoscl88-04(~/progs/cc/src)$ g++ -std=c++11 testFooCopyElision.cxx # Copy elision enabled by default
vinegupt@bhoscl88-04(~/progs/cc/src)$ ./a.out
Running: fReturnValueOptimization
Foo constructed
Running: fNamedReturnValueOptimization
Foo constructed
Foo destructed
Foo destructed
vinegupt@bhoscl88-04(~/progs/cc/src)$ g++ -std=c++11 -fno-elide-constructors testFooCopyElision.cxx # Copy elision disabled
vinegupt@bhoscl88-04(~/progs/cc/src)$ ./a.out
Running: fReturnValueOptimization
Foo constructed
Foo move constructed
Foo destructed
Foo move constructed
Foo destructed
Running: fNamedReturnValueOptimization
Foo constructed
Foo move constructed
Foo destructed
Foo move constructed
Foo destructed
Foo destructed
Foo destructed

Ich sehe, dass die Rückgabewertoptimierung, dh das Kopieren von temporären Objekten in Rückgabeanweisungen im Allgemeinen unabhängig von C++ 17 gewährleistet ist.

Die benannte Rückgabewertoptimierung von zurückgegebenen lokalen Variablen geschieht jedoch meistens, ist jedoch nicht garantiert. In einer Funktion mit unterschiedlichen Rückgabeanweisungen sehe ich, dass, wenn jede der Rückgabeanweisungen Variablen mit lokalem Gültigkeitsbereich oder Variablen mit demselben Gültigkeitsbereich zurückgibt, dies geschieht. Andernfalls, wenn in verschiedenen Rückgabeanweisungen Variablen mit unterschiedlichen Gültigkeitsbereichen zurückgegeben werden, wäre es für den Compiler schwierig, eine Kopierentfernung durchzuführen.

Es wäre schön, wenn es eine Möglichkeit gäbe, das Entfernen von Kopien zu garantieren oder eine Art Warnung zu erhalten, wenn das Entfernen von Kopien nicht durchgeführt werden kann, was die Entwickler dazu bringen würde, sicherzustellen, dass das Entfernen von Kopien durchgeführt wird, und den Code umzugestalten, wenn dies nicht möglich ist .

999610cookie-checkWie funktioniert die garantierte Entfernung von Kopien?

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

Privacy policy