
Prätorianer
unique_ptr<T>
erlaubt keine Kopierkonstruktion, sondern unterstützt Bewegungssemantik. Dennoch kann ich a zurückgeben unique_ptr<T>
aus einer Funktion und weisen den zurückgegebenen Wert einer Variablen zu.
#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. Also, wie ist es diese Linie 1
ruft den Kopierkonstruktor nicht auf und führt zu Compilerfehlern? Wenn ich die Leitung benutzen müsste 2
stattdessen wäre es sinnvoll (mit line 2
funktioniert auch, aber wir sind nicht dazu verpflichtet).
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 neugierig, wie dies implementiert ist, ist es im Compiler ein Sonderfall oder gibt es eine andere Klausel in der Sprachspezifikation, die dies ausnutzt?

fredoverflow
Gibt es eine andere Klausel in der Sprachspezifikation, die dies ausnutzt?
Ja, siehe 12.8 §34 und §35:
Wenn bestimmte Kriterien erfüllt sind, darf eine Implementierung die Kopier-/Verschiebekonstruktion eines Klassenobjekts weglassen […]
Diese Elision von Kopier-/Verschiebeoperationen, genannt Elision kopierenist erlaubt […]
in einer return-Anweisung in einer Funktion mit einem Klassenrückgabetyp, wenn der Ausdruck der Name eines nichtflüchtigen automatischen Objekts ist mit demselben cv-unqualifizierten Typ wie der Rückgabetyp der Funktion […]
Wenn die Kriterien für das Ausschließen eines Kopiervorgangs erfüllt sind und das zu kopierende Objekt durch einen Lvalue gekennzeichnet ist, wird zuerst eine Überladungsauflösung durchgeführt, um den Konstruktor für die Kopie auszuwählen als ob das Objekt durch einen Rvalue bezeichnet wäre.
Wollte nur noch einen Punkt hinzufügen, dass die Rückgabe per Wert hier die Standardauswahl sein sollte, da ein benannter Wert in der return-Anweisung im schlimmsten Fall, also ohne Auslassungen in C++11, C++14 und C++17 behandelt wird als rvalue. So kompiliert beispielsweise die folgende Funktion mit der -fno-elide-constructors
Flagge
std::unique_ptr<int> get_unique() {
auto ptr = std::unique_ptr<int>{new int{2}}; // <- 1
return ptr; // <- 2, moved into the to be returned unique_ptr
}
...
auto int_uptr = get_unique(); // <- 3
Wenn das Flag beim Kompilieren gesetzt ist, finden in dieser Funktion zwei Züge (1 und 2) und später ein Zug (3) statt.

Nikola Smiljanić
Dies ist in keiner Weise spezifisch für std::unique_ptr
, gilt aber für jede Klasse, die beweglich ist. Es wird durch die Sprachregeln garantiert, da Sie nach Wert zurückgeben. Der Compiler versucht, Kopien zu entfernen, ruft einen Move-Konstruktor auf, wenn er keine Kopien entfernen kann, ruft einen Copy-Konstruktor auf, wenn er nicht verschieben kann, und kompiliert nicht, wenn er nicht kopieren kann.
Wenn Sie eine Funktion hätten, die akzeptiert std::unique_ptr
als Argument könnten Sie ihm kein p übergeben. Sie müssten den move-Konstruktor explizit aufrufen, aber in diesem Fall sollten Sie die Variable p nicht nach dem Aufruf von verwenden bar()
.
void bar(std::unique_ptr<int> p)
{
// ...
}
int main()
{
unique_ptr<int> p = foo();
bar(p); // error, can't implicitly invoke move constructor on lvalue
bar(std::move(p)); // OK but don't use p afterwards
return 0;
}

Bartosz Milewski
unique_ptr hat keinen traditionellen Kopierkonstruktor. Stattdessen hat es einen “Move-Konstruktor”, der Rvalue-Referenzen verwendet:
unique_ptr::unique_ptr(unique_ptr && src);
Eine Rvalue-Referenz (das doppelte kaufmännische Und) wird nur an einen Rvalue gebunden. Aus diesem Grund erhalten Sie eine Fehlermeldung, wenn Sie versuchen, einen lvalue unique_ptr an eine Funktion zu übergeben. Andererseits wird ein Wert, der von einer Funktion zurückgegeben wird, als Rvalue behandelt, sodass der Move-Konstruktor automatisch aufgerufen wird.
Das wird übrigens korrekt funktionieren:
bar(unique_ptr<int>(new int(44));
Der temporäre unique_ptr ist hier ein rvalue.

David Lee
Ich denke, es ist perfekt erklärt in Artikel 25 von Scott Meyers’ Effektives modernes C++. Hier ein Auszug:
Der Teil des Standard-Segens des RVO sagt weiter, dass, wenn die Bedingungen für das RVO erfüllt sind, aber Compiler sich dafür entscheiden, keine Kopierelision durchzuführen, das zurückgegebene Objekt als rvalue behandelt werden muss. Tatsächlich verlangt der Standard, dass, wenn die RVO erlaubt ist, entweder eine Kopie entfernt wird oder std::move
wird implizit auf zurückgegebene lokale Objekte angewendet.
Hier, RVO bezieht sich auf Renditeoptimierungund wenn die Voraussetzungen für die RVO erfüllt sind bedeutet, das lokale Objekt zurückzugeben, das innerhalb der Funktion deklariert ist, die Sie erwarten würden RVOwas auch in Punkt 25 seines Buches schön erklärt wird, indem auf den Standard verwiesen wird (hier die lokales Objekt enthält die von der return-Anweisung erstellten temporären Objekte). Das größte Mitnehmen aus dem Auszug ist entweder findet eine Kopielöschung statt oder std::move
wird implizit auf zurückgegebene lokale Objekte angewendet. Scott erwähnt das in Punkt 25 std::move
wird implizit angewendet, wenn der Compiler die Kopie nicht auslässt und der Programmierer dies nicht ausdrücklich tun sollte.
In Ihrem Fall ist der Code eindeutig ein Kandidat für RVO da es das lokale Objekt zurückgibt p
und die Art von p
ist derselbe wie der Rückgabetyp, was zu einer Elision der Kopie führt. Und wenn der Compiler aus irgendeinem Grund die Kopie nicht auslässt, std::move
wäre in die Linie getreten 1
.

v010dya
Eine Sache, die ich in anderen Antworten nicht gesehen habe, ist Um 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 gegeben wurde.
Das Beispiel könnte so aussehen:
class Test
{int i;};
std::unique_ptr<Test> foo1()
{
std::unique_ptr<Test> res(new Test);
return res;
}
std::unique_ptr<Test> foo2(std::unique_ptr<Test>&& t)
{
// return t; // this will produce an error!
return std::move
}
//...
auto test1=foo1();
auto test2=foo2(std::unique_ptr<Test>(new Test));
Ich möchte einen Fall erwähnen, in dem Sie std::move() verwenden müssen, sonst wird es einen Fehler geben. Fall: Wenn der Rückgabetyp der Funktion vom Typ der lokalen Variablen abweicht.
class Base { ... };
class Derived : public Base { ... };
...
std::unique_ptr<Base> Foo() {
std::unique_ptr<Derived> derived(new Derived());
return std::move(derived); //std::move() must
}
Referenz: https://www.chromium.org/developers/smart-pointer-guidelines
9889800cookie-checkRückgabe von unique_ptr aus Funktionenyes
Hypothetisch, wenn Sie a Fabrik Methode, möchten Sie lieber 1 oder 2, um die Fabrikausgabe zurückzugeben? Ich nehme an, dass dies die häufigste Verwendung von 1 wäre, weil Sie mit einer richtigen Fabrik tatsächlich möchten, dass das Eigentum an dem konstruierten Ding auf den Aufrufer übergeht.
– Xharlie
15. September 2015 um 11:10 Uhr
@Xharlie? Beide übergeben das Eigentum an der
unique_ptr
. Die ganze Frage dreht sich darum, dass 1 und 2 zwei verschiedene Möglichkeiten sind, dasselbe zu erreichen.– Prätorianer
15. September 2015 um 16:28 Uhr
In diesem Fall findet das RVO auch in c++0x statt, die Zerstörung des unique_ptr-Objekts wird einmalig durchgeführt, was danach durchgeführt wird
main
Funktion wird beendet, aber nicht, wenn diefoo
Ausgänge.– ampawd
19. Januar 2019 um 14:17 Uhr