Rückgabe von unique_ptr aus Funktionen

Lesezeit: 10 Minuten

Ruckgabe von unique ptr aus Funktionen
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?

  • 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 die foo Ausgänge.

    – ampawd

    19. Januar 2019 um 14:17 Uhr


Ruckgabe von unique ptr aus Funktionen
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.

  • @juanchopanza Meinst du das im Wesentlichen? foo() wird tatsächlich auch zerstört (wenn ihm nichts zugewiesen wurde), genau wie der Rückgabewert innerhalb der Funktion, und daher macht es Sinn, dass C++ dabei einen Move-Konstruktor verwendet unique_ptr<int> p = foo();?

    – 7 Kühe

    4. Juni 2013 um 16:10 Uhr


  • Diese Antwort sagt eine Implementierung ist erlaubt etwas zu tun … es sagt nicht, dass es muss, also wenn dies der einzige relevante Abschnitt wäre, würde das bedeuten, dass das Vertrauen auf dieses Verhalten nicht portierbar ist. Aber das finde ich nicht richtig. Ich neige dazu zu glauben, dass die richtige Antwort mehr mit dem Zugkonstruktor zu tun hat, wie in der Antwort von Nikola Smiljanic und Bartosz Milewski beschrieben.

    – Don Hatch

    22. Juli 2014 um 22:19 Uhr


  • @DonHatch Es heißt, es sei “erlaubt”, in diesen Fällen eine Kopier-/Verschiebeentfernung durchzuführen, aber wir sprechen hier nicht über eine Kopierentfernung. Es ist der zweite zitierte Absatz, der hier zutrifft, der sich auf die Copy Elision-Regeln stützt, aber selbst kein Copy Elision ist. Im zweiten Absatz gibt es keine Ungewissheit – er ist vollkommen tragbar.

    – Josef Mansfield

    24. September 2014 um 8:34 Uhr


  • @juanchopanza Mir ist klar, dass dies jetzt 2 Jahre später ist, aber hast du immer noch das Gefühl, dass dies falsch ist? Wie ich im vorherigen Kommentar erwähnt habe, geht es hier nicht um das Entfernen von Kopien. Es kommt einfach so vor, dass in den Fällen, in denen das Entfernen von Kopien gelten könnte (auch wenn es nicht mit gelten kann std::unique_ptr), gibt es eine spezielle Regel, Objekte zuerst als Rvalues ​​zu behandeln. Ich denke, das stimmt völlig mit dem überein, was Nikola geantwortet hat.

    – Josef Mansfield

    24. September 2014 um 8:36 Uhr


  • Warum erhalte ich also immer noch den Fehler “Versuch, auf eine gelöschte Funktion zu verweisen” für meinen Nur-Verschieben-Typ (entfernter Kopierkonstruktor), wenn ich ihn genau so wie in diesem Beispiel zurückgebe?

    – Trommel

    6. Mai 2019 um 7:58 Uhr

1646946016 537 Ruckgabe von unique ptr aus Funktionen
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;
}

  • @Fred – na ja, nicht wirklich. Obwohl p ist keine vorübergehende, das Ergebnis von foo(), was zurückgegeben wird, ist; es ist also ein rvalue und kann verschoben werden, wodurch die Zuweisung erfolgt main möglich. Ich würde sagen, Sie haben sich geirrt, außer dass Nikola diese Regel dann anzuwenden scheint p selbst, was ein Fehler ist.

    – Edward Seltsam

    30. November 2010 um 18:39 Uhr

  • Genau das, was ich sagen wollte, aber keine Worte fand. Ich habe diesen Teil der Antwort entfernt, da er nicht sehr klar war.

    – Nikola Smiljanić

    30. November 2010 um 18:43 Uhr


  • Ich habe eine Frage: Gibt es in der ursprünglichen Frage einen wesentlichen Unterschied zwischen Line 1 und Linie 2? Aus meiner Sicht ist es seit dem Bau dasselbe p in maines kümmert sich nur um den Typ des Rückgabetyps von foorechts?

    – Hongxu Chen

    24. September 2014 um 2:02 Uhr


  • @HongxuChen In diesem Beispiel gibt es absolut keinen Unterschied, siehe das Zitat aus dem Standard in der akzeptierten Antwort.

    – Nikola Smiljanić

    25. September 2014 um 3:22 Uhr

  • Eigentlich können Sie p danach verwenden, solange Sie ihm zuweisen. Bis dahin können Sie nicht versuchen, auf den Inhalt zu verweisen.

    – Alan

    15. März 2017 um 14:06 Uhr

1646946016 85 Ruckgabe von unique ptr aus Funktionen
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.

  • Ich denke, der Punkt ist mehr, warum kann p – “offensichtlich” ein Wert – als behandelt werden rwert in der Rücksendung return p; in der Definition von foo. Ich glaube nicht, dass es ein Problem mit der Tatsache gibt, dass der Rückgabewert der Funktion selbst “verschoben” werden kann.

    – CB Bailey

    30. November 2010 um 23:12 Uhr

  • Bedeutet das Verpacken des zurückgegebenen Werts der Funktion in std::move, dass er zweimal verschoben wird?

    Benutzer712850

    3. April 2013 um 21:42 Uhr

  • @RodrigoSalazar std::move ist nur eine ausgefallene Umwandlung von einer lvalue-Referenz (&) in eine rvalue-Referenz (&&). Die überflüssige Verwendung von std::move auf einer Rvalue-Referenz ist einfach ein Noop

    – TiMoch

    13. April 2013 um 19:37 Uhr

1646946017 313 Ruckgabe von unique ptr aus Funktionen
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.

1646946017 581 Ruckgabe von unique ptr aus Funktionen
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));

  • Es wird in der Antwort von Fredoverflow erwähnt – deutlich hervorgehoben “automatisch Objekt”. Eine Referenz (einschließlich einer Rvalue-Referenz) ist kein automatisches Objekt.

    – Toby Speight

    3. Juli 2017 um 16:15 Uhr


  • @TobySpeight Okay, tut mir leid. Ich denke, mein Code ist dann nur eine Klarstellung.

    – v010dya

    3. Juli 2017 um 16:33 Uhr

  • Danke für diese Antwort! Ich versuche seit Tagen, ein dadurch verursachtes Problem zu debuggen, und als ich diese Antwort las, wurde mir klar, was falsch war.

    – Nick Alger

    24. April 2021 um 22:16 Uhr

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

  • Es wird in der Antwort von Fredoverflow erwähnt – deutlich hervorgehoben “automatisch Objekt”. Eine Referenz (einschließlich einer Rvalue-Referenz) ist kein automatisches Objekt.

    – Toby Speight

    3. Juli 2017 um 16:15 Uhr


  • @TobySpeight Okay, tut mir leid. Ich denke, mein Code ist dann nur eine Klarstellung.

    – v010dya

    3. Juli 2017 um 16:33 Uhr

  • Danke für diese Antwort! Ich versuche seit Tagen, ein dadurch verursachtes Problem zu debuggen, und als ich diese Antwort las, wurde mir klar, was falsch war.

    – Nick Alger

    24. April 2021 um 22:16 Uhr

988980cookie-checkRückgabe von unique_ptr aus Funktionen

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

Privacy policy