Ich habe über den letzten Tag oder so über Move Constructors gelernt und versucht, an einer allgemeinen Regel der Rückgabe zu bleiben, wie die meisten Leute vorschlagen, und sind auf ein interessantes (für mich) Dilemma gestoßen.
>Nehmen wir an, ich hätte eine teure Klasse / C, die den Kopierkonstruktor, den Zuweisungsoperator, den Move-Konstruktor und den Zuweisungsoperator korrekt definiert hat. //
Erstens, dieser Codeteil macht den Kopierkonstruktor wie erwartet:
%Vor%wie folgt:
%Vor%und so (ob ich eine 1 oder 2 passiere):
%Vor%Wenn ich dazu komme, habe ich ein Problem:
%Vor%Das Übergeben einer 1 löst RVO für das Ergebnis von make_c1 aus, aber das Übergeben einer 2 löst den Kopierkonstruktor auf tmp aus.
Wenn Sie die folgende Funktion ändern, wird der Verschiebungskonstruktor stattdessen für tmp ausgelöst:
%Vor%Alles großartig und wundervoll außer ...
In diesen einfachen Beispielen wurde RVO so ziemlich ausgelöst, wie ich es mir erhofft hatte.
Aber was ist, wenn mein Code etwas komplexer ist und auf einigen Compilern RVO in dieser letzten Funktion nicht hervorruft? In diesem Fall müsste ich meinen Aufruf in std :: move nach make_c1 umbrechen, wodurch der Code bei den Compilern, die RVO hervorrufen, weniger effizient ist.
Meine Fragen sind also:
Der Compiler, mit dem ich gespielt habe, ist GCC 4.5.3 auf Cygwin.
Die implizite Rückzugsbewegung ist nur in denselben Kontexten legal, in denen RVO legal ist. Und RVO ist legal, wenn der Ausdruck der Name eines nichtflüchtigen automatischen Objekts (anders als ein Parameter function oder catch-clause) ist, mit dem gleichen cv-unqualifizierten Typ wie der Rückgabetyp der Funktion ([class.copy] / p31 / b1) ).
Wenn Sie make_c4
zu:
Dann erhalten Sie die erwartete Move-Konstruktion für den Aufruf von make_c4(2)
. Ihr make_c5
rewrite ist aus genau den Gründen, die Sie angeben, nicht wünschenswert.
Aktualisierung:
Ich hätte auch einen Verweis auf [expr.cond] / p6 / b1 einfügen sollen, der die Semantik des Bedingungsausdrucks erklärt, wenn der zweite Ausdruck ein Pr-Wert und der dritte ein L-Wert ist, aber beide den gleichen Typ haben:
Der zweite und der dritte Operand haben den gleichen Typ; das Ergebnis ist von dieser Typ. Wenn die Operanden einen Klassentyp haben, ist das Ergebnis ein prvalue temporär des Ergebnistyps, der von beiden Kopien initialisiert wird der zweite Operand oder der dritte Operand in Abhängigkeit vom Wert des erster Operand.
i.e. Dieser Absatz gibt an, dass der resultierende prvalue der Bedingung vom dritten Argument in Ihrem Beispiel copy-initialized ist. Kopierinitialisierung ist in [dcl.init] / p14 definiert. Wenn die Quelle einer Kopierinitialisierung ein Klassenwert lvalue ist, ruft dies den Kopierkonstruktor des Typs auf. Wenn die Quelle ein R-Wert ist, wird der Move-Konstruktor aufgerufen, falls einer existiert, andernfalls wird der Copy-Konstruktor aufgerufen.
Die Spezifikation des bedingten Ausdrucks erlaubt keine implizite Verschiebung von einem lvalue-Argument, selbst wenn der bedingte Ausdruck Teil eines Rückgabeausdrucks ist. Es ist möglich, dass die Sprache in der Lage gewesen wäre, solch eine implizite Bewegung zu ermöglichen, aber soweit ich weiß, wurde sie niemals vorgeschlagen. Außerdem ist die existierende Spezifikation des Bedingungsausdrucks bereits sehr kompliziert, was eine solche Änderung der Sprache umso schwieriger macht.