Versuchen Sie zu verstehen, ob die Verwendung von std::forward
mit auto&&
Variablen der richtige Weg ist, um diese Variablen zu übergeben, damit sie verschoben werden können.
Angenommen, es gibt eine Funktion:
%Vor%Und der Aufrufer - zwei Variablen, die auf rvalue und lvalue verweisen:
%Vor% Wir wissen, dass eine Variable vom Typ auto&&
eine universelle Referenz ist, weil eine Typableitung stattfindet. Das bedeutet, dass sowohl uniRefRV
als auch uniRefLV
universelle Referenzen sind.
In meinem Beispiel ist es offensichtlich, dass uniRefRV
ist rvalue und uniRefLV
ist lvalue , aber konzeptionell sind sie beide universelle Referenzen und Wenn die Definition anders ist, könnten sie entweder rvalue oder lvalue darstellen.
Nun möchte ich moveWidget()
aufrufen und diese universellen Verweistypen perfekt weiterleiten. Die Richtlinie (von Scott Meyers) sagt:
Übergeben und geben Sie rvalue-Referenzen über
std::move
, universelle Referenzen überstd::forward
zurück.
Und wenn ich die Richtlinie nicht völlig falsch interpretiere, scheint es logisch, std::forward
zu verwenden. Aber lassen Sie uns alle möglichen Entscheidungen in Betracht ziehen:
Glauben Sie, wir sollten beide Referenzen uniRefLV
und uniRefRV
gleich behandeln und welche von drei Optionen sollten wir für eine perfekte Weiterleitung verwenden?
Sie missdeuten die Leitlinie. Oder es zumindest zu wörtlich nehmen.
Ich denke, es gibt drei wichtige Dinge, die hier zu realisieren sind.
Zunächst sind hier alle Typen bekannt. Der Hinweis für universelle Referenzen gilt meist für generischen Code mit Vorlagen, bei denen Sie gar nicht wissen, ob etwas eine lvalue-Referenz oder eine rvalue-Referenz ist oder nimmt.
Zweitens nimmt die Funktion eine rvalue-Referenz: Sie müssen einen rvalue übergeben. Zeitraum. Es gibt keine Wahl hier.
Und die logische Schlussfolgerung ist, dass Sie nicht eine universelle Referenz übergeben möchten: Was immer Sie übergeben, muss ein rvalue sein, es kann niemals ein lvalue sein. Universelle Referenzen können lvalues sein (wenn sie als lvalue-Referenzen instanziiert werden). Eine universelle Referenz zu übergeben heißt: "Ich weiß nicht, um was für eine Referenz es sich handelt, und ich kann sie entweder als rvalue oder als lvalue übergeben, also gebe ich sie genau so weiter, wie ich sie bekommen habe". Der Fall in dieser Frage ist eher wie "Ich weiß genau, was ich passieren muss, also das werde ich weitergeben".
Nehmen wir an, dass der Fall nicht so simpel ist wie es scheint. Anstelle von giveMeInt
haben wir etwas ähnliches:
Und anstelle von moveMe
haben Sie etwas, das tatsächlich eine universelle Referenz benötigt und daher kein unbedingtes std::move
benötigt.
Jetzt brauchen Sie eigentlich eine perfekte Weiterleitung.
%Vor% Ich glaube, du bist hier ein wenig durcheinander geraten. Weder uniRefLV
noch uniRefRV
sind "universelle Referenzen". Sie sind nur Variablen und beide haben einen bestimmten Typ. Beide Typen sind Referenztypen, aber das ist nicht wichtig.
Um moveWidget
aufzurufen, müssen Sie in beiden Fällen std::move
verwenden, und in beiden Fällen lautet die Semantik, dass nach dem Verschieben Ihr ursprüngliches w
verschoben wurde und sich somit nicht mehr in einem bestimmten Zustand befindet . (Das Beste, was Sie damit tun können, ist es entweder zu zerstören oder neu zuzuweisen.)
Im Gegensatz dazu ist eine echte universelle Referenz ein Template-Argument und muss immer die kanonische Form haben:
%Vor%Das ist
T
tritt als Funktionsparameter T &&
und Wenn diese Bedingungen erfüllt sind, ist T &&
die universelle Referenz, und Sie übergeben sie über std::forward<T>
, wodurch der korrekte Referenztyp erhalten bleibt.
(1) .a Es verletzt nicht die Richtlinie. Sie sagen ausdrücklich "ich brauche diesen Wert nicht mehr, also nimm ihn" das ist im Grunde, wofür std :: move ist. Und wenn du weiterhin uniRefLV verwendest, nachdem du std :: move gemacht hast - das ist gegen die Richtlinie. Der Wert ist (möglicherweise) verschwunden, Sie haben ihn übergeben. Das sagt Scott .
(1) .b Das ist echt. Obwohl std :: move dort nicht benötigt wird, vereinfacht es das Lesen des Quellcodes.
(2), (3) Wir müssen kein Template-Argument für std :: forward angeben. Es soll von einem Compiler abgeleitet werden! Zumindest ist das mein Verständnis. Und ich sehe keinen Sinn, es manuell zu machen. Weiterleiten Entfernen Sie einfach die Referenzen (& amp; & amp; und & amp;) von der Typdefinition. Deshalb wird forwarding genannt und für die Datenweiterleitung verwendet. Wenn Sie eine Funktion haben: Vorlage void foo (T & amp; & tt;);
Es kann zu einem der folgenden Elemente instanziiert werden:
%Vor%Zum Beispiel:
%Vor%Wird instanziiert:
%Vor%Nicht:
%Vor% Also werden alle diese Referenzen durch einen std :: forward entfernt. Und nur *modificator* SomeType t
wird der Funktion innerhalb von foo()
übergeben, an die sie übergeben wird.