C ++ 14 automatisch erkennen "return sollte std :: move" Situation verwenden

8

Mein Verständnis ist, dass in C ++ 17 das folgende Snippet das Richtige tun soll:

%Vor%

Das heißt, in C ++ 17 soll der Compiler sowohl d1 als auch d2 als rvalues ​​für die Zwecke der Überladungsauflösung in diesen beiden return-Anweisungen behandeln. In C ++ 14 und früher war dies jedoch nicht der Fall; Die lvalue-to-rvalue-Transformation in return operands sollte nur dann gelten, wenn der Operand genau den korrekten Rückgabetyp hat.

Außerdem scheinen GCC und Clang in diesem Bereich ein verwirrendes und möglicherweise fehlerhaftes Verhalten zu haben. Den obigen Code auf Wandbox versuchen, sehe ich diese Ausgaben:

%Vor%

Also begann das als Werkzeugfrage und endete mit einer Nebenordnung von "Was ist das korrekte Verhalten für einen C ++ - Compiler?"

Meine Tooling-Frage lautet: In unserer Codebasis gibt es mehrere Orte, an denen return x; steht, die aber versehentlich eine Kopie anstelle einer Bewegung erzeugen, weil unsere Toolchain GCC 4.9.x und / oder Clang ist. Wir möchten diese Situation automatisch erkennen und std::move() nach Bedarf einfügen. Gibt es eine einfache Möglichkeit, dieses Problem zu erkennen? Vielleicht könnten wir einen Clang-Tidy-Check oder eine -Wfoo -Flags aktivieren?

Aber jetzt möchte ich natürlich auch wissen, was das korrekte Verhalten eines C ++ - Compilers in diesem Code ist. Zeigen diese Ausgaben GCC / Clang Fehler an? Werden sie bearbeitet? Und sollte die Sprachversion ( -std= ) eine Rolle spielen? (Ich würde denken, dass es wichtig ist, es sei denn, das korrekte Verhalten wurde über Fehlerberichte aktualisiert, die bis zu C ++ 11 zurückreichen.)

Hier ist ein vollständigerer Test , der von Barrys Antwort inspiriert wurde. Wir testen sechs verschiedene Fälle, in denen eine Umwandlung von lval zu rval wünschenswert wäre.

%Vor%

Nach Barrys Antwort scheint mir Clang 3.9+ in allen Fällen das technisch korrekte zu tun; GCC 8+ macht die wünschenswerte Sache in allen Fällen; und im Allgemeinen sollte ich aufhören zu lehren, dass Leute "nur return x und lassen Sie den Compiler DTRT" (oder lehren Sie es zumindest mit einem großen blinkenden Vorbehalt), weil in der Praxis der Compiler nicht DTRT, wenn Sie nicht verwenden einen hochmodernen (und technisch nichtkonformen) GCC.

    
Quuxplusone 11.02.2018, 02:47
quelle

1 Antwort

10

Das korrekte Verhalten ist move / copy. Sie möchten wahrscheinlich nur einen ordentlich aufgeräumten Scheck schreiben.

Die Formulierung in C ++ 17 lautet [class.copy.elision] / 3 und in C ++ 14 ist [class.copy] / 32 . Die spezifischen Wörter und die Formatierung sind unterschiedlich, aber die Regel ist die gleiche.

In C ++ 11 war der Regelwortlaut [class.copy] / 32 und wurde an die Kopierschutzregeln gebunden, wurde die Ausnahme für automatische lokale Speichervariablen in CWG 1579 als Fehlerbericht hinzugefügt. Compiler, die sich vor diesem Fehlerbericht befinden, würden sich als Kopie / Kopie verhalten. Da der Fehlerbericht jedoch gegen C ++ 11 spricht, würden Compiler, die die Änderung des Wortlauts implementieren, diese in allen Standardversionen implementieren.

Verwendung des C ++ 17-Wortlauts:

  

In den folgenden Copy-Initialization-Kontexten könnte anstelle einer Kopieroperation eine move-Operation verwendet werden:

     
  • Wenn der Ausdruck in einer return-Anweisung ein (eventuell geklammerter) ID-Ausdruck ist, der ein Objekt mit automatischer Speicherdauer im body oder parameter-declaration-clause der innersten umschließenden Funktion oder Lambda-Ausdruck deklariert, oder
  •   
  • [...]
  •   

Überladungsauflösung, um den Konstruktor für die Kopie auszuwählen, wird zuerst so ausgeführt, als ob das Objekt durch einen R-Wert bestimmt wäre. Wenn die erste Überladungsauflösung fehlschlägt oder nicht ausgeführt wurde, oder wenn der Typ des ersten Parameters des ausgewählten Konstruktors kein rvalue-Verweis auf den Objekttyp (möglicherweise cv-qualifiziert) ist, wird die Überladungsauflösung ausgeführt Betrachten wir das Objekt erneut als einen Wert.

In:

%Vor%

Wir treffen den ersten Punkt, also versuchen wir ein Unrelated mit einem rvalue vom Typ Derived zu initialisieren, was Unrelated(Derived&& ) ergibt. Das entspricht den hervorgehobenen Kriterien, also verwenden wir es und das Ergebnis ist ein Umzug.

In:

%Vor%

Wir treffen wieder die erste Kugel, aber die Überladungsauflösung findet Base(Base&& ) . Der erste Parameter des ausgewählten Konstruktors ist nicht ein rvalue-Verweis auf Derived (möglicherweise cv-qualifiziert), sodass wir die Überladungsauflösung erneut ausführen - und am Ende kopieren.

    
Barry 11.02.2018 03:07
quelle