unique_ptr<T>
erlaubt keine Kopierkonstruktion, sondern unterstützt die Verschiebungssemantik. Dennoch kann ich a zurückgebenunique_ptr<T>
von einer Funktion zurückgeben und den zurückgegebenen Wert einer Variablen zuweisen.
#include <iostream>
#include <memory>
using namespace std;
unique_ptr<int> foo()
{
unique_ptr<int> p( new int(10) );
return p; // 1
//return move( p ); // 2
}
int main()
{
unique_ptr<int> p = foo();
cout << *p << endl;
return 0;
}
Der obige Code wird kompiliert und funktioniert wie vorgesehen. Wie kommt es also, dass die Zeile 1
den Kopierkonstruktor nicht aufruft und zu Compilerfehlern führt? Wenn ich Linie benutzen müsste2
stattdessen verwenden müsste, wäre dies sinnvoll (die Verwendung von Leitung 2
funktioniert auch, aber wir müssen dies nicht tun).
Ich weiß, dass C ++ 0x diese Ausnahme zulässt, unique_ptr
da der Rückgabewert ein temporäres Objekt ist, das zerstört wird, sobald die Funktion beendet wird, wodurch die Eindeutigkeit des zurückgegebenen Zeigers garantiert wird. Ich bin gespannt, wie dies implementiert wird. Ist es speziell im Compiler enthalten oder gibt es eine andere Klausel in der Sprachspezifikation, die dies ausnutzt?
quelle
unique_ptr
. Die ganze Frage ist, dass 1 und 2 zwei verschiedene Wege sind, um dasselbe zu erreichen.main
Funktion beendet wurde, jedoch nicht, wenn die Funktion beendet wirdfoo
.Antworten:
Ja, siehe 12.8 §34 und §35:
Ich wollte nur noch einen Punkt hinzufügen, dass die Rückgabe nach Wert hier die Standardauswahl sein sollte, da im schlimmsten Fall ein benannter Wert in der return-Anweisung, dh ohne Elisionen in C ++ 11, C ++ 14 und C ++ 17, behandelt wird als ein Wert. So wird beispielsweise die folgende Funktion mit dem
-fno-elide-constructors
Flag kompiliertWenn das Flag beim Kompilieren gesetzt ist, finden in dieser Funktion zwei Züge (1 und 2) statt und später ein Zug (3).
quelle
foo()
tatsächlich auch zerstört werden wird (wenn es nichts zugewiesen wurde), genau wie der Rückgabewert innerhalb der Funktion, und daher ist es sinnvoll, dass C ++ dabei einen Verschiebungskonstruktor verwendetunique_ptr<int> p = foo();
?std::unique_ptr
), es eine spezielle Regel gibt, Objekte zuerst als rWerte zu behandeln. Ich denke, das stimmt völlig mit dem überein, was Nikola geantwortet hat.Dies ist in keiner Weise spezifisch für
std::unique_ptr
, sondern gilt für jede Klasse, die beweglich ist. Dies wird durch die Sprachregeln garantiert, da Sie nach Wert zurückkehren. Der Compiler versucht, Kopien zu löschen, ruft einen Verschiebungskonstruktor auf, wenn er keine Kopien entfernen kann, ruft einen Kopierkonstruktor auf, wenn er nicht verschoben werden kann, und kann nicht kompiliert werden, wenn er nicht kopieren kann.Wenn Sie eine Funktion hätten, die
std::unique_ptr
als Argument akzeptiert , könnten Sie p nicht an sie übergeben. Sie müssten den Verschiebungskonstruktor explizit aufrufen, aber in diesem Fall sollten Sie die Variable p nach dem Aufruf von nicht verwendenbar()
.quelle
p
nicht vorübergehend ist, ist das Ergebnis dessenfoo()
, was zurückgegeben wird, Folgendes: Somit ist es ein Wert und kann verschoben werden, was die Zuordnungmain
ermöglicht. Ich würde sagen, Sie haben sich geirrt, außer dass Nikola diese Regel dann auf sichp
selbst anzuwenden scheint, was ein Fehler ist.1
und Linie2
? Meiner Ansicht nach ist es die gleiche , da bei der Konstruktionp
inmain
, es nur Sorgen über die Art des Rückgabetypenfoo
, nicht wahr?unique_ptr hat nicht den traditionellen Kopierkonstruktor. Stattdessen hat es einen "Verschiebungskonstruktor", der rWertreferenzen verwendet:
Eine r-Wert-Referenz (das doppelte kaufmännische Und) bindet nur an einen r-Wert. Aus diesem Grund wird eine Fehlermeldung angezeigt, wenn Sie versuchen, einen lvalue unique_ptr an eine Funktion zu übergeben. Andererseits wird ein Wert, der von einer Funktion zurückgegeben wird, als r-Wert behandelt, sodass der Verschiebungskonstruktor automatisch aufgerufen wird.
Das wird übrigens richtig funktionieren:
Das temporäre unique_ptr hier ist ein rWert.
quelle
p
- "offensichtlich" ein l-Wert - in der return-Anweisung in der Definition von als r- Wert behandelt werden . Ich glaube nicht, dass es ein Problem mit der Tatsache gibt, dass der Rückgabewert der Funktion selbst "verschoben" werden kann.return p;
foo
Ich denke, es ist in Punkt 25 von Scott Meyers ' Effective Modern C ++ perfekt erklärt . Hier ist ein Auszug:
Hier RVO bezieht sich auf die Wertoptimierung zurückkehren , und wenn die Bedingungen für die RVO erfüllt sind Mittel , um das lokale Objekt deklariert innerhalb der Funktion zurückkehrt , dass man erwarten würde das tun RVO , die auch schön ist erklärt in Artikel 25 seines Buches unter Bezugnahme auf der Standard (hier enthält das lokale Objekt die temporären Objekte, die von der return-Anweisung erstellt wurden). Die größte Auswirkung auf den Auszug besteht darin, dass entweder eine Kopierelision stattfindet oder
std::move
implizit auf lokale Objekte angewendet wird, die zurückgegeben werden . Scott erwähnt in Punkt 25, dassstd::move
implizit angewendet wird, wenn der Compiler die Kopie nicht entfernt und der Programmierer dies nicht explizit tun sollte.In Ihrem Fall ist der Code eindeutig ein Kandidat für RVO, da er das lokale Objekt zurückgibt
p
und der Typ vonp
dem gleichen wie der Rückgabetyp ist, was zu einer Kopierelision führt. Und wenn der Compiler sich aus irgendeinem Grund dafür entscheidet, die Kopie nicht zu entfernen,std::move
wäre er in die Reihe getreten1
.quelle
Eine Sache, die ich in anderen Antworten nicht gesehen habe, istUm eine andere Antwort zu verdeutlichen , gibt es einen Unterschied zwischen der Rückgabe von std :: unique_ptr, die innerhalb einer Funktion erstellt wurde, und einer, die dieser Funktion zugewiesen wurde.Das Beispiel könnte folgendermaßen aussehen:
quelle