Wie funktioniert die garantierte Kopierentscheidung?


88

Auf dem Oulu ISO C ++ Standards Meeting 2016 wurde vom Normungsausschuss ein Vorschlag mit dem Titel Garantierte Kopierentfernung durch vereinfachte Wertekategorien in C ++ 17 abgestimmt.

Wie genau funktioniert die garantierte Kopierentscheidung? Deckt es einige Fälle ab, in denen die Kopierelision bereits zulässig war, oder sind Codeänderungen erforderlich, um die Kopierelision zu gewährleisten?

Antworten:


129

Eine Kopierentscheidung war unter verschiedenen Umständen zulässig. Selbst wenn dies zulässig war, musste der Code dennoch so funktionieren, als ob die Kopie nicht entfernt worden wäre. Es musste nämlich eine zugängliche Kopie und / oder einen Verschiebungskonstruktor geben.

Die garantierte Kopierentfernung definiert eine Reihe von C ++ - Konzepten neu, sodass bestimmte Umstände, unter denen Kopien / Verschiebungen entfernt werden könnten, eine Kopie / Verschiebung überhaupt nicht provozieren . Der Compiler entfernt keine Kopie. Der Standard besagt, dass ein solches Kopieren niemals stattfinden könnte.

Betrachten Sie diese Funktion:

T Func() {return T();}

Unter nicht garantierten Regeln für die Kopierelision wird ein temporäres Element erstellt und dann von diesem temporären in den Rückgabewert der Funktion verschoben. Diese Verschiebungsoperation kann entfallen, Tmuss jedoch über einen zugänglichen Verschiebungskonstruktor verfügen, auch wenn sie niemals verwendet wird.

Ähnlich:

T t = Func();

Dies ist eine Kopierinitialisierung von t. Dadurch wird die Initialisierung tmit dem Rückgabewert von kopiert Func. Es muss jedoch Tnoch ein Verschiebungskonstruktor vorhanden sein, obwohl dieser nicht aufgerufen wird.

Die garantierte Kopierentscheidung definiert die Bedeutung eines Wertausdrucks neu . Vor C ++ 17 sind Werte temporäre Objekte. In C ++ 17 ist ein prvalue-Ausdruck lediglich etwas, das eine temporäre materialisieren kann , aber noch keine temporäre.

Wenn Sie einen Wert vom Typ prvalue mit einem Wert verwenden, wird kein temporärer Wert materialisiert. Wenn Sie dies tun return T();, wird der Rückgabewert der Funktion über einen Wert initialisiert. Da diese Funktion zurückkehrt T, wird keine temporäre Funktion erstellt. Die Initialisierung des Wertes initiiert einfach direkt den Rückgabewert.

Zu verstehen ist, dass der Rückgabewert, da er ein Wert ist, noch kein Objekt ist. Es ist lediglich ein Initialisierer für ein Objekt, genau wie es T()ist.

Wenn Sie dies tun T t = Func();, initialisiert der Wert des Rückgabewerts das Objekt direkt t. Es gibt keine Phase "Temporär erstellen und kopieren / verschieben". Da Func()der Rückgabewert ein Wert ist, der äquivalent zu ist T(), twird er direkt von initialisiert T(), genau so, als ob Sie es getan hätten T t = T().

Wenn ein Wert auf eine andere Weise verwendet wird, materialisiert der Wert ein temporäres Objekt, das in diesem Ausdruck verwendet wird (oder verworfen wird, wenn kein Ausdruck vorhanden ist). Wenn Sie dies const T &rt = Func();tun würden, würde der Wert einen temporären Wert (der T()als Initialisierer verwendet wird) materialisieren , in dem die Referenz rtzusammen mit dem üblichen temporären Material zur Verlängerung der Lebensdauer gespeichert wird .

Eine Sache, die Ihnen die garantierte Elision erlaubt, ist die Rückgabe von Objekten, die unbeweglich sind. Zum Beispiel lock_guardkann nicht kopiert oder verschoben werden, so dass Sie nicht eine Funktion , die es von Wert zurückgegeben haben könnten. Aber mit garantierter Kopierentscheidung können Sie.

Garantierte Elision funktioniert auch mit direkter Initialisierung:

new T(FactoryFunction());

Wenn FactoryFunctionnach TWert zurückgegeben wird, kopiert dieser Ausdruck den Rückgabewert nicht in den zugewiesenen Speicher. Stattdessen wird Speicher zugewiesen und der zugewiesene Speicher wird direkt als Rückgabewert für den Funktionsaufruf verwendet.

Werksfunktionen, die nach Wert zurückkehren, können den Heap-zugewiesenen Speicher direkt initialisieren, ohne es zu wissen. Solange diese Funktionen intern den Regeln der garantierten Kopierentscheidung entsprechen, natürlich. Sie müssen einen Wert vom Typ zurückgeben T.

Das funktioniert natürlich auch:

new auto(FactoryFunction());

Falls Sie keine Typnamen schreiben möchten.


Es ist wichtig zu erkennen, dass die oben genannten Garantien nur für Werte gelten. Das heißt, Sie erhalten keine Garantie, wenn Sie eine benannte Variable zurückgeben:

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

In diesem Fall tmuss noch ein zugänglicher Kopier- / Verschiebungskonstruktor vorhanden sein. Ja, der Compiler kann das Kopieren / Verschieben optimieren. Der Compiler muss jedoch weiterhin die Existenz eines zugänglichen Kopier- / Verschiebungskonstruktors überprüfen.

Für die Named Return Value Optimization (NRVO) ändert sich also nichts.


1
@BenVoigt: Das Einfügen von nicht trivial kopierbaren benutzerdefinierten Typen in Register ist keine praktikable Aufgabe eines ABI, unabhängig davon, ob eine Elision verfügbar ist oder nicht.
Nicol Bolas

1
Jetzt, da die Regeln öffentlich sind, kann es sinnvoll sein, dies mit dem Konzept "Werte sind Initialisierungen" zu aktualisieren.
Johannes Schaub - litb

6
@ JohannesSchaub-litb: Es ist nur "mehrdeutig", wenn Sie viel zu viel über die Details des C ++ - Standards wissen. Für 99% der C ++ - Community wissen wir, worauf sich "garantierte Kopierelision" bezieht. Das eigentliche Papier, in dem die Funktion vorgeschlagen wird, trägt sogar den Titel "Garantierte Kopierentscheidung". Das Hinzufügen von "durch vereinfachte Wertekategorien" macht es nur verwirrend und für Benutzer schwer verständlich. Es ist auch eine Fehlbezeichnung, da diese Regeln die Regeln für Wertekategorien nicht wirklich "vereinfachen". Ob Sie es mögen oder nicht, der Begriff "garantierte Kopierentscheidung" bezieht sich auf diese Funktion und nichts anderes.
Nicol Bolas

1
Ich möchte also in der Lage sein, einen Wert aufzunehmen und herumzutragen. Ich denke, dass dies std::function<T()>wirklich nur ein (One-Shot) ist .
Yakk - Adam Nevraumont

1
@LukasSalich: Das ist eine C ++ 11 Frage. Diese Antwort bezieht sich auf eine C ++ 17-Funktion.
Nicol Bolas
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.