Der folgende Link enthält die 4 Formen des Referenzzusammenbruchs (wenn ich richtig liege, dass dies die einzigen 4 Formen sind): http://thbecker.net/articles/rvalue_references/section_08.html.
Aus dem Link:
- Aus A& & wird A&
- A& && wird zu A&
- A&& & wird zu A&
- A&& && wird zu A&&
Obwohl ich eine begründete Vermutung anstellen kann, hätte ich gerne eine kurze Erklärung für die Gründe hinter jeder dieser Regeln zum Zusammenfallen von Referenzen.
Eine verwandte Frage, wenn ich darf: Werden diese Referenz-Reduzierungsregeln verwendet? in C++11 intern durch solche STL-Dienstprogramme wie z std::move()
, std::forward()
, und dergleichen, in typischen realen Anwendungsfällen? (Hinweis: Ich frage ausdrücklich, ob die Verweisreduzierungsregeln in C++11 verwendet werden, im Gegensatz zu C++03 oder früher.)
Ich stelle diese verwandte Frage, weil mir solche C ++ 11-Dienstprogramme wie bekannt sind std::remove_reference
aber ich weiß nicht, ob die referenzbezogenen Dienstprogramme wie z std::remove_reference
werden routinemäßig in C++11 verwendet Notwendigkeit vermeiden die Regeln zum Zusammenfassen von Referenzen oder ob sie verwendet werden in Verbindung mit die Regeln zum Zusammenfallen von Referenzen.
Die Referenz-Komprimierungsregeln (außer für A& & -> A&
also C++98/03) gibt es aus einem Grund: um eine perfekte Weiterleitung zu ermöglichen.
“Perfekte” Weiterleitung bedeutet, Parameter effektiv weiterzuleiten, als ob der Benutzer die Funktion direkt aufgerufen hätte (minus Elision, die durch die Weiterleitung unterbrochen wird). Es gibt drei Arten von Werten, die der Benutzer übergeben kann: lvalues, xvalues und prvalues, und es gibt drei Möglichkeiten, wie der empfangende Speicherort einen Wert annehmen kann: per Wert, per (möglicherweise konstanter) Lvalue-Referenz und per (möglicherweise konstanter) rvalue Hinweis.
Betrachten Sie diese Funktion:
template<class T>
void Fwd(T &&v) { Call(std::forward<T>(v)); }
Nach Wert
Wenn Call
nimmt seinen Parameter nach Wert, dann muss ein Kopieren/Verschieben in diesen Parameter erfolgen. Welche hängt davon ab, was der eingehende Wert ist. Wenn der eingehende Wert ein Lvalue ist, muss er den Lvalue kopieren. Wenn der eingehende Wert ein rvalue ist (die zusammen xvalues und prvalues sind), muss er sich von ihm bewegen.
Wenn Sie anrufen Fwd
mit einem lvalue bedeuten die Typableitungsregeln von C++ dies T
wird abgeleitet als Type&
wo Type
ist der Typ des lvalue. Offensichtlich, wenn der lvalue ist const
es wird abgeleitet als const Type&
. Das bedeuten die Referenzkollapsregeln Type & &&
wird Type &
zum v
, eine Lvalue-Referenz. Genau das müssen wir anrufen Call
. Der Aufruf mit einer Lvalue-Referenz erzwingt eine Kopie, genau so, als ob wir sie direkt aufgerufen hätten.
Wenn Sie anrufen Fwd
mit einem rvalue (also: a Type
vorübergehender Ausdruck oder bestimmte Type&&
Ausdrücke), dann T
wird abgeleitet als Type
. Die zusammenfassenden Referenzregeln geben uns Type &&
was eine Bewegung/Kopie provoziert, was fast genau so ist, als hätten wir es direkt aufgerufen (minus Elision).
Per Lvalue-Referenz
Wenn Call
erhält seinen Wert durch Lvalue-Referenz, dann sollte es nur aufrufbar sein, wenn der Benutzer Lvalue-Parameter verwendet. Wenn es sich um eine const-lvalue-Referenz handelt, kann sie von allem aufgerufen werden (lvalue, xvalue, prvalue).
Wenn Sie anrufen Fwd
mit einem lvalue erhalten wir wieder Type&
als Art von v
. Dies wird an eine nicht konstante lvalue-Referenz gebunden. Wenn wir es mit einem konstanten lvalue aufrufen, erhalten wir const Type&
die nur an ein konstantes lvalue-Referenzargument in gebunden wird Call
.
Wenn Sie anrufen Fwd
mit einem xvalue erhalten wir wieder Type&&
als Art von v
. Dieser Wille nicht ermöglichen es Ihnen, eine Funktion aufzurufen, die einen nicht konstanten Lvalue akzeptiert, da ein xvalue nicht an eine nicht konstante Lvalue-Referenz gebunden werden kann. Es kann an eine konstante Lvalue-Referenz gebunden werden, also if Call
verwendet a const&
könnten wir anrufen Fwd
mit einem x-Wert.
Wenn Sie anrufen Fwd
mit einem prvalue erhalten wir wieder Type&&
, also funktioniert alles wie vorher. Sie können keine temporäre Funktion an eine Funktion übergeben, die einen nicht konstanten L-Wert annimmt, daher wird unsere Weiterleitungsfunktion bei dem Versuch, dies zu tun, ebenfalls ersticken.
Per rvalue-Referenz
Wenn Call
erhält seinen Wert durch Rvalue-Referenz, dann sollte es nur aufrufbar sein, wenn der Benutzer xvalue- oder rvalue-Parameter verwendet.
Wenn Sie anrufen Fwd
mit einem lvalue erhalten wir Type&
. Dies wird nicht an einen rvalue-Referenzparameter gebunden, sodass ein Kompilierungsfehler resultiert. EIN const Type&
wird auch nicht an einen rvalue-Referenzparameter gebunden, so dass es immer noch fehlschlägt. Und genau das würde passieren, wenn wir anrufen würden Call
direkt mit einem lvalue.
Wenn Sie anrufen Fwd
mit einem xvalue erhalten wir Type&&
was funktioniert (Lebenslauf-Qualifikation natürlich trotzdem).
Dasselbe gilt für die Verwendung eines Prvalue.
std::weiter
std::forward selbst verwendet Regeln zum Zusammenfassen von Referenzen auf ähnliche Weise, um eingehende rvalue-Referenzen als xvalues (Funktionsrückgabewerte, die Type&&
sind xvalues) und eingehende lvalue-Referenzen als lvalues (returning Type&
).
Die Regeln sind eigentlich ziemlich einfach. Rvalue reference
ist ein Verweis auf einen temporären Wert, der nicht über den Ausdruck hinaus bestehen bleibt, der ihn verwendet – im Gegensatz zu lvalue reference
die auf persistente Daten verweist. Wenn Sie also einen Verweis auf persistente Daten haben, egal mit welchen anderen Verweisen Sie sie kombinieren, sind die tatsächlich referenzierten Daten ein Lvalue – dies deckt die ersten 3 Regeln ab. Die vierte Regel ist ebenfalls natürlich – rvalue-Referenz auf rvalue-Referenz ist immer noch eine Referenz auf nicht persistente Daten, daher wird rvalue-Referenz zurückgegeben.
Ja, die C++11-Dienstprogramme verlassen sich auf diese Regeln, die von Ihrem Link bereitgestellte Implementierung entspricht den echten Headern: http://en.cppreference.com/w/cpp/utility/forward
Und ja, die Zusammenbruchsregeln zusammen mit der Regel zum Abzug von Vorlagenargumenten werden bei der Verwendung angewendet std::move
und std::forward
Dienstprogramme, genau wie in Ihrem Link erklärt.
Die Verwendung von Typeigenschaften wie z remove_reference
Das hängt wirklich von Ihren Bedürfnissen ab; move
und forward
Deckung für die lässigsten Fälle.
Es würde Sie wahrscheinlich interessieren Dieses Video von Scott Meyers.
– Benjamin Lindley
5. Dezember 2012 um 14:50 Uhr
Betrachte es jetzt. Danke!
– Dan Nissenbaum
5. Dezember 2012 um 14:53 Uhr
“typische reale Anwendungsfälle” – Perfekte Weiterleitung (was ich als sehr nützlichen Anwendungsfall in der realen Welt bezeichnen würde) baut vollständig auf diesen Referenzkollapsregeln auf (und könnte wiederum der praktische Grund für die Gestaltung dieser Regeln gewesen sein, wie sie sind, denke ich). Durch die Einführung einer neuen Art von Referenzen zur Nutzung der Bewegungssemantik erhielten sie auch die Möglichkeit, die entsprechenden Collapsing-Regeln zu definieren, um eine perfekte Weiterleitung zu ermöglichen und somit zwei Probleme mit einem einzigen Feature zu lösen.
– Christian Rau
5. Dezember 2012 um 15:49 Uhr
Ich denke
A& & becomes A&
ist nicht Referenz zusammenbricht.– Nawaz
24. September 2015 um 12:06 Uhr