Warum wird das Objekt nicht sofort von std :: move zerstört zurückgegeben?

7

Ich habe den folgenden Code getestet:

%Vor%

Die Ausgabe war:

%Vor%

Ich habe jedoch erwartet :

%Vor%

Entsprechend dem Quellcode der Funktion move:

%Vor%

Das zurückgegebene Objekt ist ein richtiger Wert. Warum wird es nicht sofort zerstört?



-------------------------------------------------- ---------------
Ich glaube, ich habe gerade den Wert rvalue und rvalue verwechselt.
Ich habe meinen Code geändert:

%Vor%

Und jetzt bekomme ich, was ich erwartet habe:

%Vor%     
Pony279 27.03.2013, 16:04
quelle

6 Antworten

13

Das Verschieben von einem Objekt ändert nicht seine Lebensdauer, sondern nur seinen aktuellen Wert. Ihr Objekt foo wird bei der Rückkehr von main zerstört, was auf Ihre Ausgabe folgt.

Außerdem bewegt sich std::move nicht vom Objekt. Es gibt nur eine rvalue-Referenz zurück, deren Referenz das Objekt ist, wodurch es möglich ist, möglich von dem Objekt zu verschieben.

    
Steve Jessop 27.03.2013, 16:20
quelle
5

Objekte werden zerstört, wenn sie den Gültigkeitsbereich verlassen. Der Wechsel von einem Objekt ändert das nicht; abhängig davon, was der Move-Konstruktor oder der Move-Zuweisungsoperator tut, kann der Status des Objekts nach dem Verschieben unterschiedlich sein, wurde aber noch nicht zerstört. Daher ist die praktische Regel, dass das Verschieben von einem Objekt muss lass es in einem Zustand, der zerstört werden kann.

Darüber hinaus, wie @ R.MartinhoFernandes zeigt, macht std::move nichts. Es ist der Move-Konstruktor oder der Movie- Zuweisungsoperator des Objekts, der alles Notwendige erledigt und nicht in einem Aufruf von std::move ; Es wird angewendet, wenn das Objekt "move-from" verwendet wird, um ein neues Objekt zu erstellen (Konstruktor verschieben) oder einem vorhandenen Objekt zugewiesen wird (Zuweisungsoperator verschieben). So:

%Vor%     
Pete Becker 27.03.2013 16:20
quelle
1

Weil im allgemeinen Fall die Verschiebung in einer anderen Übersetzungseinheit erfolgen kann. In Ihrem Beispiel wurde das Objekt nicht einmal verschoben, sondern nur als beweglich markiert. Dies bedeutet, dass der Aufrufende von std::move nicht wissen kann, ob das Objekt verschoben wurde oder nicht. Er weiß nur, dass es ein Objekt gibt und dass es den Destruktor am Ende des Bereichs / Lebenszeit dieses Objekts. std::move markiert das Objekt nur als beweglich , es führt den Verschiebevorgang nicht durch oder erstellt eine verschobene Kopie, die weiter verschoben werden kann oder ähnliches.

Überlegen Sie:

%Vor%

Wie würde im obigen Beispiel die Übersetzungseinheit 2 entscheiden, ob der Destruktor nach dem std::move ?

aufgerufen werden soll oder nicht?     
Daniel Frey 27.03.2013 16:09
quelle
1

Sie werden durch den Namen verwirrt - std::move bewegt tatsächlich nichts. Es konvertiert nur eine lvalue-Referenz in eine rvalue-Referenz und wird verwendet, um jemanden dazu zu bringen, etwas zu bewegen.

Wo std::move nützlich ist, wenn Sie eine überladene Funktion haben, die entweder einen lvalue- oder einen rvalue-Verweis verwendet. Wenn Sie eine solche Funktion mit einer einfachen Variablen aufrufen, bedeutet die Überladungsauflösung, dass Sie die Version mit dem Wert lvalue aufrufen. Sie können einen expliziten Aufruf zu std::move hinzufügen, um stattdessen die rvalue-Referenzversion aufzurufen. Es sind keine Bewegungen involviert, außer innerhalb der Funktion, die die rvalue-Referenz übernimmt.

Nun ist der Grund, warum move genannt wird, die allgemeine Verwendung, bei der Sie zwei Konstruktoren haben, von denen einer eine lvalue-Referenz (allgemein als Kopierkonstruktor bezeichnet) und eine eine rvalue-Referenz (häufig Move Constructor genannt). In diesem Fall bedeutet das Hinzufügen eines expliziten Aufrufs zu std::move , dass Sie den Move-Konstruktor anstelle des Kopierkonstruktors aufrufen.

Im allgemeineren Fall ist es üblich, Funktionen zu überladen, die lvalue / rvalue-Referenzen verwenden, wobei die lvalue-Version eine Kopie des Objekts erstellt und die rvalue-Version das Objekt verschiebt (implizit wird das Quellobjekt geändert, um Speicher zu übernehmen) es benutzt).

    
Chris Dodd 27.03.2013 16:25
quelle
1

A std::move ändert die Lebensdauer von Objekten nicht. Grob gesagt ist es nichts anderes als ein static_cast , das einen nichtkonstanten Wert für eine nicht konstante rvalue-Referenz liefert.

Die Nützlichkeit davon ist die Überladungsauflösung. In der Tat nehmen einige Funktionen Parameter durch eine Konstantenwertreferenz (z. B. Kopierkonstruktoren) und andere durch Nicht-Konstantenwertreferenz (z. B. Verschiebungskonstruktoren). Wenn das übergebene Objekt ein temporäres Objekt ist, ruft der Compiler die zweite Überladung auf. Die Idee ist, dass kurz nachdem die Funktion aufgerufen wurde, eine temporäre Datei nicht mehr verwendet werden kann (und zerstört wird). Daher könnte die zweite Überladung Ressourcen des Temporären übernehmen, anstatt sie zu bewältigen.

Der Compiler wird dies jedoch nicht für ein nicht temporäres Objekt tun (oder, um korrekter für einen lvalue zu sein). Der Grund ist, dass das übergebene Objekt einen Namen hat, der im Gültigkeitsbereich bleibt und daher lebendig ist (wie Ihr Code demonstriert). Daher sind möglicherweise noch interne Ressourcen erforderlich, und es wäre ein Problem, wenn sie auf das andere Objekt verschoben wurden. Sie können jedoch den Compiler anweisen, die zweite Überladung mit std::move aufzurufen. Das Argument wird in eine rvalue-Referenz umgewandelt, und bei Überladungsauflösung wird die zweite Überladung aufgerufen.

Der leicht geänderte Code unten veranschaulicht diesen Punkt.

%Vor%

Die Ausgabe ist

%Vor%

Update: (Nachdem die Frage so geändert wurde, dass mymove verwendet wird)

Beachten Sie, dass der neue Code nicht genau das gibt, was Sie am Anfang gesagt haben. In der Tat meldet es zwei Aufrufe an ~foo() anstelle von eins.

Von den angezeigten Adressen können wir sehen, dass das ursprüngliche Objekt vom Typ foo , nämlich f , ganz am Ende zerstört wird. Genau wie früher mit dem Originalcode. Wie viele gezeigt haben, wird f nur am Ende seines Geltungsbereichs zerstört (der Körper der Funktion main ). Dies ist immer noch der Fall.

Der zusätzliche Aufruf von ~foo() wird gemeldet, bevor statement "mymove(f);" done. ein anderes Objekt zerstört, das eine Kopie von f ist. Wenn Sie einen Berichtkopiekonstruktor zu foo hinzufügen:

%Vor%

Dann erhalten Sie eine Ausgabe ähnlich wie:

%Vor%

Wir können daraus schließen, dass der Aufruf von mymove einen Aufruf an den Kopierkonstruktor liefert, um f auf ein anderes foo -Objekt zu kopieren. Dann wird dieses neu erstellte Objekt zerstört, bevor die Ausführung die Zeile erreicht, die statement "move(f);" done.

anzeigt

Die natürliche Frage ist jetzt wo diese Kopie von kommt? Beachten Sie den Rückgabetyp von mymove :

%Vor%

In diesem Beispiel wird nach einer Vereinfachung aus Gründen der Übersichtlichkeit auf foo reduziert. Das heißt, mymove gibt einen foo nach Wert zurück. Daher wird eine Kopie erstellt, um ein temporäres Objekt zu erstellen. Wie ich bereits sagte, wird ein temporäres Objekt direkt nach dem Ausdruck zerstört, der es erstellt, um es ausgewertet zu bekommen (nun, es gibt Ausnahmen zu dieser Regel, aber sie gelten nicht für diesen Code). Das erklärt den zusätzlichen Aufruf von ~foo() .

    
Cassio Neri 27.03.2013 16:27
quelle
0

Angenommen, Code wie folgt:

%Vor%

In diesem Fall kann der Aufrufer von bar nicht wissen, dass die Funktion in einigen Fällen den Destruktor des übergebenen Objekts aufruft. Es fühlt sich verantwortlich für dieses Objekt und stellt sicher, dass es seinen Destruktor zu einem späteren Zeitpunkt aufruft.

Die Regel ist also, dass move ein gültiges Objekt in ein anderes, aber immer noch gültiges Objekt verwandeln kann. Noch gültig bedeutet, dass Aufrufmethoden oder der Destruktor für dieses Objekt immer noch wohldefinierte Ergebnisse liefern. Streng genommen macht move selbst nichts anderes, als dem Empfänger eine solche Referenz mitzuteilen, dass es das Objekt ändern kann, wenn dies sinnvoll ist. Es ist also der Empfänger, z.B. Konstruktor verschieben oder Zuweisungsoperator verschieben, der die eigentliche Verschiebung ausführt. Diese Operationen werden normalerweise das Objekt überhaupt nicht ändern oder einige Zeiger auf nullptr oder einige Längen auf Null oder etwas Ähnliches setzen. Sie werden jedoch niemals den Destruktor aufrufen, da diese Aufgabe dem Eigentümer des Objekts überlassen wird.

    
MvG 27.03.2013 16:22
quelle

Tags und Links