c++11 Rückgabewertoptimierung oder Umzug? [duplicate]

Lesezeit: 9 Minuten

c11 Ruckgabewertoptimierung oder Umzug duplicate
Elvis Dukaj

Ich verstehe nicht, wann ich es verwenden soll std::move und wann ich den Compiler optimieren lassen soll… zum Beispiel:

using SerialBuffer = vector< unsigned char >;

// let compiler optimize it
SerialBuffer read( size_t size ) const
{
    SerialBuffer buffer( size );
    read( begin( buffer ), end( buffer ) );
    // Return Value Optimization
    return buffer;
}

// explicit move
SerialBuffer read( size_t size ) const
{
    SerialBuffer buffer( size );
    read( begin( buffer ), end( buffer ) );
    return move( buffer );
}

Welche soll ich verwenden?

  • Nach dem, was ich bisher gelesen habe, scheint der allgemeine Konsens eher darauf zu zählen, dass der Compiler RVO verwendet als move ausdrücklich: Moderne Compiler sind intelligent genug, um RVO so ziemlich überall zu verwenden, und es ist effizienter als move. Aber das ist nur “Hörensagen”, wohlgemerkt, daher bin ich sehr an einer dokumentierten Erklärung interessiert.

    – sym

    4. Juli 2013 um 15:25 Uhr

  • Für den Rückgabewert einer lokalen Variablenfunktion müssen Sie nie explizit verschieben. Es ist eine implizite Bewegung dorthin.

    – Martin B

    4. Juli 2013 um 15:26 Uhr

  • Der Compiler hat dann die freie Wahl: Wenn es möglich ist, verwendet er RVO und wenn nicht, kann er immer noch verschieben (und wenn für den Typ keine Verschiebung möglich ist, wird er kopieren).

    – Martin B

    4. Juli 2013 um 15:27 Uhr

  • @MartinBa, sag niemals nie 😉 Du brauchst eine explizite Verschiebung, wenn die lokale Variable nicht den gleichen Typ wie der Rückgabetyp hat, z std::unique_ptr<base> f() { auto p = std::make_unique<derived>(); p->foo(); return p; }aber wenn die Typen gleich sind, wird es sich bewegen, wenn möglich (und diese Bewegung könnte entfallen)

    – Jonathan Wakely

    4. Juli 2013 um 16:14 Uhr


  • Der Vollständigkeit halber wurde das, was @JonathanWakely gesagt hat, in einem Fehlerbericht behandelt, und zumindest die neueren Versionen von gcc und clang benötigen keinen expliziten Umzug dorthin.

    – Etarion

    5. Dezember 2016 um 18:19 Uhr

Verwenden Sie ausschließlich die erste Methode:

Foo f()
{
  Foo result;
  mangle(result);
  return result;
}

Dieser Wille bereits Erlauben Sie die Verwendung des Bewegungskonstruktors, falls einer verfügbar ist. Tatsächlich kann eine lokale Variable an eine rvalue-Referenz in a gebunden werden return Anweisung genau dann, wenn das Weglassen von Kopien erlaubt ist.

Ihre zweite Version verbietet aktiv das Entfernen von Kopien. Die erste Version ist allgemein besser.

  • Auch wenn das Entfernen von Kopien deaktiviert ist (-fno-elide-constructors) wird der Move-Konstruktor aufgerufen.

    – Maggyero

    21. April 2020 um 0:06 Uhr

  • @Maggyero: -fno-elide-constructors deaktiviert nicht die Kopierelision, sondern die Optimierung des Rückgabewerts. Ersteres ist eine Sprachregel, die Sie nicht “deaktivieren” können; Letzteres ist eine Optimierung, die sich diese Regel zunutze macht. In der Tat war mein einziger Punkt, dass Sie, selbst wenn die Rückgabewertoptimierung nicht verwendet wird, immer noch Bewegungssemantik verwenden können, die Teil desselben Satzes von Sprachregeln ist.

    – Kerrek SB

    21. April 2020 um 11:33 Uhr

  • GCC-Dokumentation an -fno-elide-constructors: “Der C++-Standard erlaubt es einer Implementierung, auf die Erstellung einer temporären Datei zu verzichten, die nur zum Initialisieren eines anderen Objekts desselben Typs verwendet wird. Die Angabe dieser Option deaktiviert diese Optimierung und zwingt G++, den Kopierkonstruktor in allen Fällen aufzurufen. Diese Option bewirkt auch, dass G++ um triviale Member-Funktionen aufzurufen, die andernfalls inline erweitert würden. In C++17 muss der Compiler diese Temporäre weglassen, aber diese Option wirkt sich immer noch auf triviale Member-Funktionen aus.”

    – Maggyero

    21. April 2020 um 13:03 Uhr

  • @Maggyero: Klingt nach einem Fehler in der Dokumentation, insbesondere klingt es so, als ob der Wortlaut der Dokumentation nicht für C ++ 11 aktualisiert wurde. Fehler melden? @JonathanWakely?

    – Kerrek SB

    21. April 2020 um 14:08 Uhr


  • Vor C++ 17 (C++ 11 und C++ 14), die -fno-elide-constructors Kompilierungsoption deaktiviert alle copy elisions, das heißt für glvalue/prvalue-Objektinitialisierer von return-Anweisungen (diese copy elisions heißen jeweils NRVO/RVO), variable prvalue-Objektinitialisierer, throw-Ausdrucks-glvalue-Objektinitialisierer und catch-Klausel-glvalue-Objektinitialisierer. Seit C++ 17 ist die Kopierelision für Prvalue-Objektinitialisierer von Rückgabeanweisungen und variable Prvalue-Objektinitialisierer obligatorisch, daher deaktiviert die Option jetzt nur die Kopierelision in den verbleibenden Fällen.

    – Maggyero

    21. April 2020 um 14:40 Uhr


c11 Ruckgabewertoptimierung oder Umzug duplicate
Jamin Gray

Alle Rückgabewerte sind entweder bereits moved oder optimiert, so dass es nicht notwendig ist, sich explizit mit Rückgabewerten zu bewegen.

Compiler dürfen den Rückgabewert automatisch verschieben (um die Kopie zu optimieren) und sogar die Verschiebung optimieren!

Abschnitt 12.8 des Standardentwurfs n3337 (C++11):

Wenn bestimmte Kriterien erfüllt sind, darf eine Implementierung die Copy/Move-Konstruktion eines Klassenobjekts weglassen, selbst wenn der Copy/Move-Konstruktor und/oder -Destruktor für das Objekt Seiteneffekte haben. In solchen Fällen behandelt die Implementierung die Quelle und das Ziel des ausgelassenen Kopier-/Verschiebevorgangs einfach als zwei verschiedene Arten, auf dasselbe Objekt zu verweisen, und die Zerstörung dieses Objekts erfolgt zu einem späteren Zeitpunkt, zu dem die beiden Objekte gewesen wären ohne die Optimierung zerstört. Diese Elision von Kopier-/Verschiebeoperationen, genannt Elision kopierenist unter den folgenden Umständen zulässig (die kombiniert werden können, um mehrere Kopien zu entfernen):

[…]

Beispiel:

class Thing {
public:
Thing();
   ~Thing();
   Thing(const Thing&);
};

Thing f() {
   Thing t;
   return t;
}

Thing t2 = f();

Hier können die Elision-Kriterien kombiniert werden, um zwei Aufrufe des Kopierkonstruktors von class zu eliminieren Thing: das Kopieren des lokalen automatischen Objekts t in das temporäre Objekt für den Rückgabewert der Funktion f()
und das Kopieren dieses temporären Objekts in Objekt t2. Effektiv die Konstruktion des lokalen Objekts t
kann als direktes Initialisieren des globalen Objekts angesehen werden t2, und die Zerstörung dieses Objekts erfolgt beim Programmende. Hinzufügen eines Bewegungskonstruktors zu Thing hat den gleichen Effekt, aber es wird die Konstruktion vom temporären Objekt verschoben t2 das ist eliminiert. — Ende Beispiel ]

Wenn die Kriterien für die Elision eines Kopiervorgangs erfüllt sind oder erfüllt wären, abgesehen von der Tatsache, dass das Quellobjekt ein Funktionsparameter ist und das zu kopierende Objekt durch einen Lvalue gekennzeichnet ist, ist die Überladungsauflösung zur Auswahl des Konstruktors für die Kopie zuerst so ausgeführt, als ob das Objekt durch einen Rvalue bezeichnet wäre. Wenn die Überladungsauflösung fehlschlägt oder wenn der Typ des ersten Parameters des ausgewählten Konstruktors kein Rvalue-Verweis auf den Typ des Objekts ist (möglicherweise CV-qualifiziert), wird die Überladungsauflösung erneut durchgeführt, wobei das Objekt als Lvalue betrachtet wird.

  • Ich mag das ganze “Compiler können X”-Argument nicht besonders. Die Frage erfordert keinen Rückgriff auf einen Compiler. Es geht rein um die Sprache. Und es ist nichts „optional“ oder vage darüber, ob „ein Umzug“ stattfindet. Die Sprache ist vollkommen klar, welche Arten von Konstruktorparametern an den Rückgabewert (der ein xvalue ist) gebunden werden können; Überladungsauflösung erledigt den Rest.

    – Kerrek SB

    4. Juli 2013 um 18:08 Uhr


  • Es geht nicht darum, was Compiler können, sondern darum, was die großen Compiler können tun tun. Das explizite Verschieben von Dingen könnte den Compilern im Weg stehen, Dinge noch besser zu machen als das Verschieben. Jeder Compiler, der fortgeschritten genug ist, um Ihnen explizites Verschieben zu ermöglichen, ist mit ziemlicher Sicherheit fortgeschritten genug, um die Rückgabewerte automatisch zu verschieben – denn im Gegensatz zu anderen Situationen, in denen Sie möglicherweise explizit verschieben möchten, ist der Rückgabewert für Compiler sehr einfach als guter Ort zu erkennen zu optimieren (weil jede Rückgabe eine Garantie dafür ist, dass der Wert in der Funktion, die die Rückgabe ausführt, nicht weiter verwendet wird).

    – Jamin Gray

    4. Juli 2013 um 22:22 Uhr

  • @Damon: Nun, irgendwie. Es Compiler könnten Verschieben Sie den Rückgabewert (und speichern Sie eine Kopie), aber sie tun es oft nicht. Stattdessen verwenden sie wo immer möglich copy-ellison, wodurch die Kopie gespeichert wird und der Umzug. Sie weisen einfach direkt an die Variable zu, die das Ergebnis der Funktion erhält, anstatt einer temporären, die zurückgegeben und später zugewiesen wird. Das manuelle Verschieben der Variablen ist nie besser und oft etwas (nur geringfügig) schlechter als das, was der Compiler tut. Der Compiler fällt zurück auf Bewegungssemantik, würde aber wenn möglich lieber RVO verwenden. So ist zumindest mein Verständnis.

    – Jamin Grey

    14. November 2013 um 1:07 Uhr


  • Alle Rückgabewerte sind bereits verschoben bzw. ausoptimiert” Nicht, wenn die Typen nicht übereinstimmen: groups.google.com/a/isocpp.org/forum/#!msg/std-proposals/…

    – cdyson37

    5. Mai 2015 um 12:24 Uhr


  • @ cdyson37 Interessant, ich bin diesem Eckfall noch nie begegnet. Glücklicherweise wird es nicht einmal ohne std::move() kompiliert. Ich versuche herauszufinden, ob dieses Beispiel tatsächlich einen Teil der beabsichtigten Sprachfunktionen demonstriert oder eine zufällige Eigenart von vorlagenbasierten Elementfunktionen ausnutzt (in diesem Fall der vorlagenbasierte Move-Konstruktor von std::unique_ptr()).

    – Jamin Grey

    7. Mai 2015 um 17:28 Uhr


1647078015 977 c11 Ruckgabewertoptimierung oder Umzug duplicate
Oktalist

Es ist ganz einfach.

return buffer;

Wenn Sie dies tun, wird entweder NRVO auftreten oder nicht. Wenn es dann nicht passiert buffer wird umgezogen.

return std::move( buffer );

Wenn Sie dies tun, dann NVRO wird nicht passieren, und buffer wird umgezogen.

Es gibt also nichts zu gewinnen, wenn Sie es verwenden std::move hier, und viel zu verlieren.


Es gibt eine Ausnahme* von der obigen Regel:

Buffer read(Buffer&& buffer) {
    //...
    return std::move( buffer );
}

Wenn buffer eine rvalue-Referenz ist, sollten Sie verwenden std::move. Dies liegt daran, dass Referenzen nicht für NRVO geeignet sind, also ohne
std::move es würde zu einer Kopie von einem lvalue führen.

Dies ist nur eine Instanz der Regel “immer move rvalue-Referenzen und forward universelle Referenzen”, die Vorrang vor der Regel “niemals move ein Rückgabewert”.

* Ab C++20 kann diese Ausnahme vergessen werden. Rvalue-Referenzen in return Anweisungen werden jetzt implizit verschoben.

  • Sehr wichtige Ausnahme, danke. Ich bin gerade in meinem Code darauf gestoßen.

    – Michail

    31. März 2018 um 17:08 Uhr

  • Was für ein komischer Zustand für eine Programmiersprache, in der man Speicher-Mnemoniken verwenden muss, um einen Entscheidungsbaum zu codieren, wie man eine einfache Sache wie die Rückgabe eines Werts ohne Kopie macht. Werden Move-Semantik und Rvalues ​​allgemein als Erfolg des cpp-Designs angesehen? Sie sind sicherlich eine komplexe Lösung für ein scheinbar einfaches Problem. Zusammen mit der impliziten Verwendung von NVRO ergibt dies sicherlich ein sehr verwirrendes Design.

    – Hund

    29. Oktober 2020 um 7:06 Uhr


  • @ldog, wie bei vielen Designentscheidungen, die sich nicht nur auf C++ konzentrieren, ist es fast immer ein Gleichgewicht zwischen Vor- und Nachteilen. Eine versehentliche manuelle Unterdrückung von RVO/NRVO auf diese Weise scheint mir ein akzeptables Risiko zu sein, wenn man alle Vorteile von rvalue-Referenzen berücksichtigt, insbesondere wenn die Fehler sehr explizit über return std::move(… gemacht werden. Und seitdem rvalue-Funktionsparameter sind seit C++11 neu in der Sprache, bestehender früherer Code oder ‘etablierte Stilgewohnheiten’ werden nicht so wahrscheinlich versehentlich gebrochen.Das garantierte Entfernen von Kopien seit C++17 hilft weiter, die Dinge hier im Auge zu behalten.

    – Sekundi

    14. Januar 2021 um 11:16 Uhr

Wenn Sie eine lokale Variable zurückgeben, verwenden Sie sie nicht move(). Dadurch kann der Compiler NRVO verwenden, und wenn dies nicht möglich ist, darf der Compiler weiterhin eine Verschiebung durchführen (lokale Variablen werden zu R-Werten innerhalb von a return Aussage). Verwenden move() würde in diesem Zusammenhang einfach NRVO verhindern und den Compiler dazu zwingen, eine Verschiebung (oder eine Kopie, falls eine Verschiebung nicht verfügbar ist) zu verwenden. Wenn Sie etwas anderes als eine lokale Variable zurückgeben, ist NRVO sowieso keine Option und Sie sollten es verwenden move() wenn (und nur wenn) Sie beabsichtigen, das Objekt zu stehlen.

993290cookie-checkc++11 Rückgabewertoptimierung oder Umzug? [duplicate]

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

Privacy policy