Wenn Sie Code wie lesen
auto&& var = foo();
Wo foo
gibt eine Funktion nach Wert des Typs zurück T
. Dann var
ist ein lWert vom Typ rWertreferenz auf T
. Aber wofür bedeutet das var
? Heißt das, wir dürfen die Ressourcen von stehlen var
? Gibt es vernünftige Situationen, in denen Sie auto&&
dem Leser Ihres Codes mitteilen sollten , wie Sie es tun, wenn Sie a zurückgeben, unique_ptr<>
um mitzuteilen, dass Sie das ausschließliche Eigentum haben? Und was ist zum Beispiel, T&&
wenn T
es sich um einen Klassentyp handelt?
Ich möchte nur verstehen, ob es andere Anwendungsfälle auto&&
als die in der Vorlagenprogrammierung gibt. wie die in den Beispielen in diesem Artikel besprochenen Universal References von Scott Meyers.
auto&&
? Ich habe darüber nachgedacht, warum eine bereichsbasierte for-Schleifeauto&&
als Beispiel erweitert wird, bin aber noch nicht dazu gekommen. Vielleicht kann jeder, der antwortet, es erklären.foo
Rückkehr zerstört, und das Speichern eines r-Wert-Refs klingt wie UB to ne.foo
könnte zum Beispiel so aussehen :int foo(){return 1;}
.Antworten:
Wenn
auto&& var = <initializer>
Sie verwenden, sagen Sie: Ich akzeptiere jeden Initialisierer, unabhängig davon, ob es sich um einen l-Wert- oder einen r-Wert-Ausdruck handelt, und ich werde seine Konstanz beibehalten . Dies wird normalerweise für die Weiterleitung verwendet (normalerweise mitT&&
). Der Grund, warum dies funktioniert, ist, dass eine "universelle Referenz"auto&&
oderT&&
an irgendetwas gebunden ist .Man könnte sagen, warum nicht einfach ein verwenden,
const auto&
weil das auch an irgendetwas gebunden ist? Das Problem bei der Verwendung einerconst
Referenz ist, dass es istconst
! Sie können es später nicht mehr an nicht konstante Referenzen binden oder Mitgliedsfunktionen aufrufen, die nicht markiert sindconst
.Stellen Sie sich als Beispiel vor, Sie möchten einen erhalten
std::vector
, nehmen Sie einen Iterator zu seinem ersten Element und ändern Sie den Wert, auf den dieser Iterator zeigt, auf irgendeine Weise:Dieser Code wird unabhängig vom Initialisierungsausdruck problemlos kompiliert. Die Alternativen, um
auto&&
auf folgende Weise zu scheitern:Funktioniert also
auto&&
perfekt! Ein Beispiel für eine solche Verwendungauto&&
ist eine bereichsbasiertefor
Schleife. Siehe meine andere Frage für weitere Details.Wenn Sie dann
std::forward
Ihreauto&&
Referenz verwenden, um die Tatsache beizubehalten, dass es ursprünglich entweder ein l-Wert oder ein r-Wert war, lautet Ihr Code: Nachdem ich Ihr Objekt entweder aus einem l-Wert- oder einem r-Wert-Ausdruck erhalten habe, möchte ich die Wertigkeit beibehalten, die es ursprünglich hatte hatte, damit ich es am effizientesten nutzen kann - dies könnte es ungültig machen. Wie in:Dies ermöglicht es
use_it_elsewhere
, die Eingeweide aus Gründen der Leistung (Vermeidung von Kopien) herauszureißen, wenn der ursprüngliche Initialisierer ein veränderbarer Wert war.Was bedeutet dies, ob wir Ressourcen stehlen können oder wann
var
? Nun, da derauto&&
Wille an irgendetwasvar
gebunden ist , können wir unmöglich versuchen, uns selbst die Eingeweide herauszureißen - es kann sehr gut ein Wert oder sogar eine Konstante sein. Wir können es jedochstd::forward
auf andere Funktionen übertragen, die sein Inneres völlig verwüsten können. Sobald wir dies tun, sollten wir unsvar
in einem ungültigen Zustand befinden.Wenden wir dies nun auf den Fall an
auto&& var = foo();
, wie in Ihrer Frage angegeben, bei dem foo einen By-T
Wert zurückgibt . In diesem Fall wissen wir sicher, dass die Art vonvar
als abgeleitet wirdT&&
. Da wir mit Sicherheit wissen, dass es sich um einen Wert handelt, benötigen wir keinestd::forward
Erlaubnis, um seine Ressourcen zu stehlen. In diesem speziellen Fall sollte der Leser in dem Wissen, dass diefoo
Rückgabe nach Wert erfolgt , einfach Folgendes lesen: Ich nehme einen r-Wert-Verweis auf die temporäre Rückgabe vonfoo
, damit ich mich glücklich davon entfernen kann.Als Nachtrag denke ich, dass es erwähnenswert ist, wann ein Ausdruck wie
some_expression_that_may_be_rvalue_or_lvalue
auftauchen könnte, abgesehen von einer Situation, in der sich Ihr Code möglicherweise ändert. Hier ist ein erfundenes Beispiel:Hier
get_vector<T>()
ist dieser schöne Ausdruck, der je nach generischem Typ entweder ein l-Wert oder ein r-Wert sein kannT
. Wir ändern im Wesentlichen den Rückgabetyp vonget_vector
durch den Vorlagenparameter vonfoo
.Wenn wir aufrufen
foo<std::vector<int>>
,get_vector
wirdglobal_vec
by value zurückgegeben, was einen rvalue-Ausdruck ergibt. Wenn wir aufrufenfoo<std::vector<int>&>
,get_vector
wird alternativ alsglobal_vec
Referenz zurückgegeben, was zu einem lvalue-Ausdruck führt.Wenn wir es tun:
Wir erhalten erwartungsgemäß die folgende Ausgabe:
Wenn Sie die Änderungen waren
auto&&
im Code zu einemauto
,auto&
,const auto&
, oder ,const auto&&
dann werden wir nicht das Ergebnis bekommen wir wollen.Eine alternative Möglichkeit, die Programmlogik basierend darauf zu ändern, ob Ihre
auto&&
Referenz mit einem l-Wert- oder einem r-Wert-Ausdruck initialisiert wurde, besteht in der Verwendung von Typmerkmalen:quelle
T vec = get_vector<T>();
innerhalb der Funktion foo sagen ? Oder vereinfache ich es auf ein absurdes Niveau :)Zunächst empfehle ich , meine Antwort als Nebenlesung zu lesen, um Schritt für Schritt zu erklären, wie die Ableitung von Vorlagenargumenten für universelle Referenzen funktioniert.
Nicht unbedingt. Was ist, wenn
foo()
plötzlich eine Referenz zurückgegeben wird oder Sie den Anruf geändert haben, aber vergessen haben, die Verwendung von zu aktualisierenvar
? Oder wenn Sie generischen Code verwenden und sich der Rückgabetypfoo()
abhängig von Ihren Parametern ändern kann?Denken Sie
auto&&
daran, genau das gleiche zu sein wieT&&
intemplate<class T> void f(T&& v);
, denn es ist (fast † ) genau das. Was machen Sie mit universellen Referenzen in Funktionen, wenn Sie sie weitergeben oder in irgendeiner Weise verwenden müssen? Sie verwendenstd::forward<T>(v)
, um die ursprüngliche Wertkategorie zurückzugewinnen. Wenn es ein Wert war, bevor es an Ihre Funktion übergeben wurde, bleibt es ein Wert, nachdem es übergeben wurdestd::forward
. Wenn es sich um einen r-Wert handelt, wird er wieder zu einem r-Wert (denken Sie daran, dass eine benannte r-Wert-Referenz ein l-Wert ist).Also, wie benutzt man es
var
generisch richtig? Verwenden Siestd::forward<decltype(var)>(var)
. Dies funktioniert genauso wiestd::forward<T>(v)
in der obigen Funktionsvorlage. Wenn dies der Fallvar
istT&&
, erhalten Sie einen Wert zurück, und wenn dies der Fall istT&
, erhalten Sie einen Wert zurück.Zurück zum Thema: Was sagen uns
auto&& v = f();
undstd::forward<decltype(v)>(v)
in einer Codebasis? Sie sagen uns, dassv
dies auf die effizienteste Weise erworben und weitergegeben wird. Denken Sie jedoch daran, dass eine solche Variable nach dem Weiterleiten möglicherweise verschoben wurde. Daher ist es falsch, sie weiter zu verwenden, ohne sie zurückzusetzen.Persönlich verwende ich
auto&&
generischen Code, wenn ich eine modifizierbare Variable benötige . Das perfekte Weiterleiten eines R-Werts ändert sich, da der Verschiebevorgang möglicherweise seinen Mut stiehlt. Wenn ich nur faul sein möchte (dh den Typnamen nicht buchstabieren möchte, auch wenn ich ihn kenne) und nicht ändern muss (z. B. wenn nur Elemente eines Bereichs gedruckt werden), bleibe ich dabeiauto const&
.†
auto
ist in so weit anders,auto v = {1,2,3};
dassv
einstd::initializer_list
, währendf({1,2,3})
wird ein Abzugsfehler sein.quelle
foo()
ein Wert vom Typ zurückkehrtT
, dannvar
(dieser Ausdruck) wird ein L - Wert und seine Art sein (dieser Ausdruck) wird ein R - Wert in Bezug gesetztT
(dhT&&
).Stellen Sie sich einen Typ
T
mit einem Verschiebungskonstruktor vor und nehmen Sie anverwendet diesen Verschiebungskonstruktor.
Verwenden wir nun eine Zwischenreferenz, um die Rendite von zu erfassen
foo
:Dies schließt die Verwendung des Verschiebungskonstruktors aus, sodass der Rückgabewert kopiert anstatt verschoben werden muss (selbst wenn wir ihn
std::move
hier verwenden, können wir nicht durch eine const ref verschieben).Wenn wir jedoch verwenden
Der Verschiebungskonstruktor ist weiterhin verfügbar.
Und um Ihre anderen Fragen zu beantworten:
Das erste, was Xeo sagt, ist, dass ich X so effizient wie möglich übergebe , egal welcher Typ X ist. Wenn Sie also Code sehen, der
auto&&
intern verwendet wird, sollte dies mitteilen, dass die Verschiebungssemantik gegebenenfalls intern verwendet wird.Wenn eine Funktionsvorlage ein Argument vom Typ annimmt
T&&
, bedeutet dies, dass das übergebene Objekt möglicherweise verschoben wird. Durch die Rückgabeunique_ptr
erhält der Aufrufer explizit den Besitz. AnnahmeT&&
kann entfernen Besitz des Anrufers (wenn ein Umzug Ctor existiert, etc.).quelle
ref
undrvref
beide Werte sind. Wenn Sie den Verschiebungskonstruktor möchten, müssen Sie schreibenT t(std::move(rvref))
.auto const &
?auto&&
und was sagen Sie dem Leser Ihres Codes mitauto&&
?Die
auto &&
Syntax verwendet zwei neue Funktionen von C ++ 11:Mit diesem
auto
Teil kann der Compiler den Typ basierend auf dem Kontext ableiten (in diesem Fall den Rückgabewert). Dies ist ohne Referenzqualifikationen (so dass Sie angeben , ob Sie wollenT
,T &
oderT &&
für eine davon abgeleitete TypenT
).Das
&&
ist die neue Bewegungssemantik. Ein Typ, der die Verschiebungssemantik unterstützt, implementiert einen KonstruktorT(T && other)
, der den Inhalt im neuen Typ optimal verschiebt. Dadurch kann ein Objekt die interne Darstellung austauschen, anstatt eine tiefe Kopie durchzuführen.Dies ermöglicht Ihnen so etwas wie:
So:
führt eine Kopie des zurückgegebenen Vektors durch (teuer), aber:
tauscht die interne Darstellung des Vektors (der Vektor von
foo
und der leere Vektor vonvar
) aus, ist also schneller.Dies wird in der neuen For-Loop-Syntax verwendet:
Wobei die for-Schleife einen
auto &&
auf den Rückgabewert von hältfoo
unditem
eine Referenz auf jeden Wert in istfoo
.quelle
auto&&
wird nichts bewegen, es wird nur eine Referenz machen. Ob es sich um eine l-Wert- oder eine r-Wert-Referenz handelt, hängt von dem Ausdruck ab, der zum Initialisieren verwendet wird.std::vector
undstd::string
bewegungskonstruierbar sind. Dies hat nichts mit der Art von zu tunvar
.