Gibt die Rückgabe einer lokalen Variablen eine Kopie zurück und zerstört das Original?

Lesezeit: 9 Minuten

Benutzer-Avatar
f877576

Ich habe diese Frage gesehen Wann ist ein Objekt “außerhalb des Geltungsbereichs”?

Ich habe mir die Antwort von Mr.sparc_spread angesehen und darin ein Problem gefunden (ich habe Ihre Antwort nicht vollständig analysiert). in diesem Abschnitt seiner Antwort:

Circle myFunc () {
    Circle c (20);
    return c;
}
// The original c went out of scope. 
// But, the object was copied back to another 
// scope (the previous stack frame) as a return value.
// No destructor was called.

Er hat gesagt, dass “kein Destruktor aufgerufen wurde.” Aber wenn ich versuche, diesen Code auszuführen (der von mir geschrieben wurde):

   /* Line number 1 */ #include <iostream>
   /* Line number 2 */ #include <string>
   /* Line number 3 */ using namespace std;
   /* Line number 4 */ class test {
   /* Line number 5 */ public:
   /* Line number 6 */  test(int p) {
   /* Line number 7 */      cout << "The constructor ( test(int p) ) was called"<<endl;
   /* Line number 8 */  }
   /* Line number 9 */  test(test&&c)noexcept  {
   /* Line number 10 */        cout << "The constructor ( test(test && c) ) was called" << endl;
   /* Line number 11 */ }
   /* Line number 12 */     ~test() {
   /* Line number 13 */         cout << "The distructor was called" << endl;
   /* Line number 14 */     }
   /* Line number 15 */ };
   /* Line number 16 */ test function() {
   /* Line number 17 */     test i(8);
   /* Line number 18 */     return i;
   /* Line number 19 */ } 
   /* Line number 20 */ int main()
   /* Line number 21 */ {
   /* Line number 22 */     test o=function();
   /* Line number 23 */     return 0;
   /* Line number 24 */ }

Die Ausgabe :

The constructor ( test(int p) ) was called
The constructor ( test(test && c) ) was called
The distructor was called
The distructor was called

Die Ausgabe meines Codes zeigt also Folgendes:

1- Zwei Konstruktoren wurden aufgerufen ( und das ist nicht der Punkt, den ich diskutieren möchte. Also werde ich nicht diskutieren ( Warum, wann oder wie ) heißen zwei Konstruktoren ? )

2- Zwei Distruktoren wurden aufgerufen

und wenn ich den Debugger verwende ( um zu wissen , wann der erste Distruktor aufgerufen wurde ) habe ich festgestellt , dass der erste Distruktor in der Zeile Nummer 18 aufgerufen wird ( die Zeile Nummer 18 in meinem Code ) . Wenn er ( Mr. sparc_spread ) meine Frage sieht und zustimmt, wird er Ihre Antwort bearbeiten.

Und am Ende . Ist mein Standpunkt richtig?

Und es tut mir leid für diese großartige Community, dass sie diesen Weg benutzt hat, um meinen Standpunkt auszudrücken, aber ich habe keinen anderen Weg. und es tut mir leid für sie herr sparc_spread sie sind eine person mit großer erfahrung . Bitte nimm diese Frage nicht persönlich.

Und ich habe noch eine Frage an Sie ( Mr. sparc_spread )

als Sie ( Mr. sparc_spread ) sagten: „Hier ist also ein klassisches Beispiel für einen Stapelrahmen (eine Ausführung einer Funktion) und eine darin deklarierte lokale Variable, die den Geltungsbereich verlässt, sobald der Stapelrahmen beendet wird – sobald die Funktion beendet ist : “

in diesem Teil: “sobald der Stapelrahmen beendet wird”

Meinten Sie das: “sobald das Programm diesen Rahmen verlässt”?

Wenn Ihre Antwort ja lautet, bearbeiten Sie bitte auch diesen Abschnitt in Ihrer Antwort

  • Erster Konstrukteur: test i(8); Zweiter Konstruktor: Wenn der zurückgegebene Wert in die verschoben wird o des main Funktion (die this Objekt wird ein Zeiger auf sein o). Erste Zerstörung: Wenn die Lebenszeit von i endet. Zweite Zerstörung: Wenn die Lebenszeit von o Ende.

    – Irgendein Programmierer-Typ

    25. Mai um 19:26 Uhr


  • Ich bekomme keinen Move-Konstruktor und nur einen Destruktoraufruf, weil NRVO. godbolt.org/z/v8Kxvo79c

    – Fred Larson

    25. Mai um 19:31 Uhr

  • @Some Programmierer Dude Also wird der Destruktor für i aufgerufen (in dem Code, der von mir geschrieben wurde). und der zweite Konstruktor für o ( in der Hauptfunktion ) und deshalb ist der Konstruktor ( test(test && c) der zweite Konstruktor ( weil der zurückgegebene Wert ein rvalue ist ), richtig?

    – f877576

    25. Mai um 19:38 Uhr


  • Der Compiler darf hier aggressiv optimieren und kann das Bauen, Kopieren und Zerstören eliminieren i. Dies ist eine der wenigen Stellen in der Sprache, an der ein beobachtbares Verhalten wie die Diagnosemeldungen, die Ihr Code ausgibt, weggelassen werden kann. Je nachdem, wie “schlau” oder aggressiv der Compiler bei der Suche nach Optimierungen ist, können Sie das Leben und den Tod von sehen oder nicht i. Die Antwort von sparc_spread kann also für Sie richtig sein oder auch nicht, abhängig von Ihren angeforderten Tools und Optimierungen.

    – Benutzer4581301

    25. Mai um 19:42 Uhr


  • Eine Anmerkung zu Debug-Builds. Sie sind absichtlich dumm. Um den Code so darzustellen, wie er geschrieben wurde, um das Debuggen zu erleichtern, führen sie im Allgemeinen KEINE Optimierung durch. GCC und Clang scheinen dies mit oder ohne Optimierung zu optimieren, es sei denn, Sie verlangen keine Elision. Hier ist Freds Link mit -fno-elide-constructors: godbolt.org/z/vvrefajz9

    – Benutzer4581301

    25. Mai um 20:02 Uhr

Benutzer-Avatar
Anoop Rana

Gibt die Rückgabe einer lokalen Variablen eine Kopie zurück und zerstört das Original?

Die endgültige Antwort auf Ihre Frage ist, dass es darauf ankommt, ob oder nicht Optimierung aktiviert. Lassen Sie uns also jeden Fall einzeln besprechen. Beachten Sie auch, dass, da die angegebene Ausgabe in der ursprünglichen Frage für C++17 gilt, die folgende Diskussion auch für dasselbe gilt (C++17 und höher).

Mit Optimierung

Hier sehen wir, was passiert, wenn die Optimierung (NRVO) aktiviert ist.

class test {
public:
test(int p) {
    cout << "The constructor ( test(int p) ) was called: "<<this<<endl;
}
test(test&&c)noexcept  {
       cout << "The constructor ( test(test && c) ) was called: "<<this << endl;
}
    ~test() {
        cout << "The distructor was called: "<<this << endl;
    }
};
test function() {
    test i(8);
    return i;
} 
int main()
{
    test o=function();
    return 0;
}

Das Ausgang des Programms ist (mit aktiviertem NRVO):

The constructor ( test(int p) ) was called: 0x7fff78e42887   <-----object o construction
The distructor was called: 0x7fff78e42887                    <-----object o destruction

Die obige Ausgabe kann mit der Optimierung namens Return Value Optimization (alias NRVO) verstanden werden, wie in beschrieben kopiere elison welche Staaten:

Unter folgenden Umständen Die Compiler dürfen, müssen aber nicht auf die Konstruktion von Klassenobjekten zum Kopieren und Verschieben (seit C++11) verzichten, selbst wenn der Konstruktor zum Kopieren/Verschieben (seit C++11) und der Destruktor beobachtbare Nebenwirkungen haben. Die Objekte werden direkt in den Speicher konstruiert, wohin sie sonst kopiert/verschoben würden. Dies ist eine Optimierung: Selbst wenn sie stattfindet und der Konstruktor zum Kopieren/Verschieben (seit C++11) nicht aufgerufen wird, muss sie immer noch vorhanden und zugänglich sein (als ob überhaupt keine Optimierung stattgefunden hätte), sonst ist das Programm krank. gebildet:

  • In einer return-Anweisung, wenn der Operand der Name eines nichtflüchtigen Objekts mit automatischer Speicherdauer ist, das kein Funktionsparameter oder Catch-Klausel-Parameter ist und das vom gleichen Klassentyp ist (ignoriert die cv-Qualifikation) wie der Rückgabetyp der Funktion. Diese Variante der Kopierunterdrückung ist als NRVO bekannt, „Named Return Value Optimization“.

(Hervorhebung von mir)

Wenden wir dies auf unser oben angegebenes Beispiel an und versuchen, die Ausgabe zu verstehen. Die Variable mit dem Namen i ist eine lokale Variable, was bedeutet, dass sie eine automatische Speicherdauer hat, und daher ist es den Compilern gemäß der oben zitierten Aussage erlaubt (aber nicht erforderlich!), das Objekt direkt in den Speicher für die benannte Variable zu konstruieren o. Das heißt, es ist als ob Sie schrieb:

test o(5); //equivalent to this due to NRVO

Hier sehen wir also zuerst den Aufruf des konvertierenden Konstruktors test::test(int) für Objekt o und dann der Destruktoraufruf für dieses Objekt o.

Ohne Optimierung

Du hast die Option zu deaktivieren diese Optimierung durch die Verwendung der -fno-elide-constructors Flagge. Und wenn dasselbe Programm mit diesem Flag ausgeführt wird, wird die Ausgang des Programms werden:

The constructor ( test(int p) ) was called: 0x7ffda9d94fe7        <-----object i construction
The constructor ( test(test && c) ) was called: 0x7ffda9d95007    <-----object o construction 
The distructor was called: 0x7ffda9d94fe7                         <-----object i destruction
The distructor was called: 0x7ffda9d95007                         <-----object o destruction

Diesmal da haben wir die geliefert -fno-elide-constructors Flag an den Compiler, NRVO ist deaktiviert. Das bedeutet, dass der Compiler jetzt nicht auf die Copy/Move-Konstruktion entsprechend der return-Anweisung verzichten kann return i;. Das wiederum bedeutet, dass zuerst das Objekt i wird mit dem Konvertierungskonstruktor erstellt test::test(int) und somit sehen wir die allererste Zeile in der Ausgabe.

Als nächstes wird diese lokale Variable mit dem Namen i wird mit dem Move-Konstruktor verschoben test::test(test&&) und daher sehen wir die zweite Zeile der Ausgabe. Beachten Sie, dass das Objekt o wird gebaut direkt von diesem bewegten prvalue direkt aufgrund Pflichtexemplar elison da Sie C++17 verwenden.

Als nächstes die lokale Variable i wird mit dem Destruktor zerstört test::~test() und wir sehen die dritte Zeile in der Ausgabe.

Endlich das Objekt o wird zerstört und wir sehen die vierte Zeile der Ausgabe.

In diesem Fall ist es als ob Sie schrieb:

test o = std::move(test(5)); //equivalent to this

  • Wie funktioniert in Ihrem Code: Verknüpfung das Flag -fno-elide-constructors wirkt sich auf die Ausgabe aus, obwohl Sie C++17 verwenden ( ab C++17 hat das Flag -fno-elide-constructors keine Auswirkung auf die Ausgabe ) ? und beachten Sie, dass Sie, wenn Sie denselben Code mit C++14 ausprobieren, 3 Konstruktoren haben werden

    – f877576

    26. Mai um 5:52 Uhr


  • @f877576 In C++17 gibt es obligatorisches Copy Elison und daher sehen wir nur 2 Aufrufe an den Konstruktor mit dem Flag, aber in C++14 und C++11 gibt es nicht obligatorisches Copy Elison und daher sehen wir 3 Aufrufe an der Konstruktor mit Flag . Das Flag wirkt sich auf die Ausgabe in C++17 aus, wenn i wird mit zurückgegeben return i; denn dazu gehört auch die noch nicht obligatorische NRVO. Insbesondere in C++17 o wird aber direkt aufgebaut und somit kein dritter Aufruf an den ctor vor C++17 wird es einen dritten Aufruf an den ctor geben, weil o wird sein nicht direkt gebaut werden aus dem verschobenen Prvalue.

    – Anoop Rana

    26. Mai um 6:14 Uhr


  • @f877576 Beachten Sie, dass dieser zusätzliche dritte Aufruf des Konstruktors in C++14 aus zwei Gründen erfolgt: 1) Wir haben benutzt -fno-elide-constructors 2) In C++14, o wird nicht direkt aus dem verschobenen Prvalue konstruiert. Aber stattdessen (o) wird mit dem Move-Konstruktor erstellt, und wenn Sie bemerken, werden Sie sehen, dass dieser dritte Aufruf an den Move-Konstruktor geht.

    – Anoop Rana

    26. Mai um 6:20 Uhr


  • Okay, was ich aus Ihren beiden Kommentaren verstehe, ist: Es gibt zwei von ( optmizations ) Mandatory und NON-Mandatory und in C++17und darüber elision copy ist Mandatory und NRVO ist NON-Mandatory Also, wenn Sie verwenden -fno-elide-constructors mit C++17 und oben nur die NRVO wird deaktiviert UND aktiviert C++14 elision copy ist NON-Mandatory und NRVO ist auch NON-Mandatory Also, wenn Sie verwenden -fno-elide-constructors mit C++14 das NRVO wird deaktiviert und die elision copy wird auch deaktiviert. Verstehe ich Ihre beiden Kommentare vollkommen richtig? @Anoop Rana

    – f877576

    29. Mai um 22:29 Uhr


  • @ f877576 Ja, im Grunde hast du es verstanden.

    – Anoop Rana

    30. Mai um 3:19 Uhr

1019070cookie-checkGibt die Rückgabe einer lokalen Variablen eine Kopie zurück und zerstört das Original?

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

Privacy policy