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, T
muss 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 t
mit dem Rückgabewert von kopiert Func
. Es muss jedoch T
noch 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()
, t
wird 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 rt
zusammen 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_guard
kann 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 FactoryFunction
nach T
Wert 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 t
muss 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.
std::function<T()>
wirklich nur ein (One-Shot) ist .