Verschieben der Semantik in C ++ - Verschieben der Rückgabe lokaler Variablen

10

Meines Wissens nach kann der Compiler in C ++ 11, wenn Sie eine lokale Variable von einer Funktion nach Wert zurückgeben, diese Variable als R-Wert-Referenz behandeln und sie aus der Funktion 'verschieben', um sie zurückzugeben (if RVO / NRVO passiert natürlich nicht stattdessen).

Meine Frage ist, kann dies nicht vorhandenen Code brechen?

Betrachten Sie den folgenden Code:

#include <iostream>
#include <string>

struct bar
{
  bar(const std::string& str) : _str(str) {}
  bar(const bar&) = delete;
  bar(bar&& other) : _str(std::move(other._str)) {other._str = "Stolen";}
  void print() {std::cout << _str << std::endl;}

  std::string _str;
};

struct foo
{
  foo(bar& b) : _b(b) {}
  ~foo() {_b.print();}

  bar& _b;
};

bar foobar()
{
  bar b("Hello, World!");
  foo f(b);

  return std::move(b);
}

int main()
{
  foobar();
  return EXIT_SUCCESS;
}

Meine Gedanken waren, dass es einem Destruktor eines lokalen Objekts möglich sein würde, auf das Objekt zu verweisen, das implizit verschoben wird, und daher unerwartet ein "leeres" Objekt zu sehen. Ich habe versucht, dies zu testen (siehe http://ideone.com/ZURoeT ), aber ich habe das 'richtige' Ergebnis ohne das explizite std::movein erhalten foobar(). Ich vermute, das lag an NRVO, aber ich habe nicht versucht, den Code neu zu ordnen, um das zu deaktivieren.

Stimmt es, dass diese Transformation (die zu einem Verschieben der Funktion führt) implizit erfolgt und vorhandenen Code beschädigen kann?

UPDATE Hier ist ein Beispiel, das zeigt, wovon ich spreche. Die folgenden zwei Links beziehen sich auf denselben Code. http://ideone.com/4GFIRu - C ++ 03 http://ideone.com/FcL2Xj - C ++ 11

Wenn Sie sich die Ausgabe ansehen, ist es anders.

Ich denke, diese Frage wird nun, wurde dies beim Hinzufügen einer impliziten Verschiebung zum Standard berücksichtigt, und es wurde entschieden, dass es in Ordnung war, diese bahnbrechende Änderung hinzuzufügen, da diese Art von Code selten genug ist. Ich frage mich auch, ob Compiler in solchen Fällen warnen werden ...

Bwmat
quelle
Eine Bewegung muss das Objekt immer in einem zerstörbaren Zustand belassen.
Zan Lynx
Ja, aber das ist nicht die Frage. Pre-c ++ 11-Code könnte davon ausgehen, dass sich der Wert einer lokalen Variablen nicht einfach aufgrund der Rückgabe ändert, sodass diese implizite Verschiebung diese Annahme aufheben könnte.
Bwmat
Das habe ich in meinem Beispiel versucht zu erläutern. Über Destruktoren können Sie den Status (einer Teilmenge) der lokalen Variablen einer Funktion überprüfen, nachdem die return-Anweisung ausgeführt wurde, aber bevor die Funktion tatsächlich zurückgegeben wird.
Bwmat
Dies ist eine ausgezeichnete Frage mit dem Beispiel, das Sie hinzugefügt haben. Ich hoffe, dass dies mehr Antworten von Profis bekommt, die dies erklären können. Das einzige echte Feedback, das ich geben kann, ist: Aus diesem Grund sollten Objekte normalerweise keine nicht besitzenden Ansichten zu Daten haben. Es gibt tatsächlich viele Möglichkeiten, unschuldig aussehenden Code zu schreiben, der fehlerhaft ist, wenn Sie Objekten Ansichten geben, die keine eigenen Ansichten besitzen (rohe Zeiger oder Referenzen). Ich kann dies in einer richtigen Antwort erläutern, wenn Sie möchten, aber ich vermute, das ist nicht das, worüber Sie wirklich hören möchten. Übrigens ist bereits bekannt, dass 11 vorhandenen Code beschädigen kann, indem beispielsweise neue Schlüsselwörter definiert werden.
Nir Friedman
Ja, ich weiß, dass C ++ 11 nie behauptet hat, keinen alten Code zu brechen, aber das ist ziemlich subtil und wäre wirklich leicht zu übersehen (keine Compilerfehler, Warnungen, Segfaults)
Bwmat

Antworten:

8

Scott Meyers veröffentlichte in comp.lang.c ++ (August 2010) ein Problem, bei dem die implizite Generierung von Verschiebungskonstruktoren die Invarianten der C ++ 03-Klasse brechen könnte:

struct X
{
  // invariant: v.size() == 5
  X() : v(5) {}

  ~X() { std::cout << v[0] << std::endl; }

private:    
  std::vector<int> v;
};

int main()
{
    std::vector<X> y;
    y.push_back(X()); // X() rvalue: copied in C++03, moved in C++0x
}

Hier ist das Problem, dass in C ++ 03 Xeine Invariante hatte, dass sein vMitglied immer 5 Elemente hatte. X::~X()rechnete mit dieser Invariante, aber der neu eingeführte Verschiebungskonstruktor bewegte sich von vund setzte dadurch seine Länge auf Null.

Dies hängt mit Ihrem Beispiel zusammen, da die gebrochene Invariante nur im XDestruktor des lokalen Objekts erkannt wird (wie Sie sagen, kann ein Destruktor eines lokalen Objekts auf das Objekt verweisen, das implizit verschoben wird, und daher unerwartet ein leeres Objekt sehen).

C ++ 11 versucht, ein Gleichgewicht zwischen dem Brechen eines Teils des vorhandenen Codes und dem Bereitstellen nützlicher Optimierungen basierend auf Verschiebungskonstruktoren zu erreichen.

Das Komitee entschied zunächst, dass Verschiebungskonstruktoren und Verschiebungszuweisungsoperatoren vom Compiler generiert werden sollten, wenn sie nicht vom Benutzer bereitgestellt werden.

Dann wurde entschieden, dass dies tatsächlich Grund zur Besorgnis war, und die automatische Generierung von Verschiebungskonstruktoren und Verschiebungszuweisungsoperatoren wurde so eingeschränkt, dass es viel weniger wahrscheinlich, wenn auch nicht unmöglich ist, dass vorhandener Code beschädigt wird (z. B. explizit definierter Destruktor).

Es ist verlockend zu glauben, dass es ausreicht, die Generierung impliziter Verschiebungskonstruktoren zu verhindern, wenn ein benutzerdefinierter Destruktor vorhanden ist, dies ist jedoch nicht der Fall ( N3153 - Implizite Verschiebung muss für weitere Details ausgeführt werden).

In N3174 - Bewegen oder nicht bewegen Stroupstrup sagt:

Ich halte dies eher für ein Sprachdesignproblem als für ein einfaches Abwärtskompatibilitätsproblem. Es ist leicht zu vermeiden, dass alter Code beschädigt wird (z. B. einfach Verschiebungsoperationen aus C ++ 0x entfernen), aber ich sehe, dass C ++ 0x eine bessere Sprache ist, indem Verschiebungsoperationen allgegenwärtig gemacht werden, ein Hauptziel, für das es sich möglicherweise lohnt, einige C + zu brechen +98 Code.

Manlio
quelle