Was ist “rvalue Referenz für * das”?

Ein Vorschlag namens “rvalue reference for * this” in clangs C ++ 11 Statusseite.

Ich habe ziemlich viel über Rvalue-Referenzen gelesen und sie verstanden, aber ich glaube nicht, dass ich davon weiß. Mit den Begriffen konnte ich auch nicht viele Ressourcen im Web finden.

Es gibt einen Link zu dem Vorschlagspapier auf der Seite: N2439 (Erweitern der Bewegungssemantik auf * this), aber ich bekomme auch nicht viele Beispiele von dort.

Worum geht es bei dieser function?

Erstens, “ref-qualifiers for * this” ist nur eine “Marketing-Aussage”. Die Art von *this ändert sich nie, siehe unten in diesem Beitrag. Mit dieser Formulierung ist es viel einfacher zu verstehen.

Als nächstes wählt der folgende Code die function aus, die basierend auf dem ref-Qualifikationsmerkmal des “impliziten Objektparameters” der function aufgerufen werden soll:

 // t.cpp #include  struct test{ void f() &{ std::cout < < "lvalue object\n"; } void f() &&{ std::cout << "rvalue object\n"; } }; int main(){ test t; tf(); // lvalue test().f(); // rvalue } 

Ausgabe:

 $ clang++ -std=c++0x -stdlib=libc++ -Wall -pedantic t.cpp $ ./a.out lvalue object rvalue object 

Die ganze Sache ist getan, um Ihnen zu erlauben, die Tatsache auszunutzen, wenn das Objekt, auf das die function angerufen wird, ein rvalue ist (unbenanntes temporäres zum Beispiel). Nehmen Sie den folgenden Code als ein weiteres Beispiel:

 struct test2{ std::unique_ptr heavy_resource; test2() : heavy_resource(new int[500]) {} operator std::unique_ptr() const&{ // lvalue object, deep copy std::unique_ptr p(new int[500]); for(int i=0; i < 500; ++i) p[i] = heavy_resource[i]; return p; } operator std::unique_ptr() &&{ // rvalue object // we are garbage anyways, just move resource return std::move(heavy_resource); } }; 

Das mag ein bisschen künstlich sein, aber Sie sollten die Idee bekommen.

Beachten Sie, dass Sie die cv-Qualifier ( const und volatile ) und Ref-Qualifiers ( & und && ) kombinieren können.


Hinweis: Viele Standard-Anführungszeichen und Überladung Auflösung Erklärung nach hier!

† Um zu verstehen, wie das funktioniert, und warum die Antwort von @ Nicol Bolas zumindest teilweise falsch ist, müssen wir uns ein wenig in den C ++ - Standard vertiefen (der Teil, der erklärt, warum @ Nicols Antwort falsch ist, ist ganz unten) nur daran interessiert).

Welche function aufgerufen wird, wird durch einen process bestimmt, der Überladungsauflösung genannt wird . Dieser process ist ziemlich kompliziert, also berühren wir nur das, was uns wichtig ist.

Zunächst ist es wichtig zu sehen, wie die Überladungsauflösung für Elementfunktionen funktioniert:

§13.3.1 [over.match.funcs]

p2 Der Satz von Kandidatenfunktionen kann sowohl Member- als auch Nicht-Member-functionen enthalten, die gegen die gleiche Argumentliste aufgetriggers werden. Damit diese Argument- und Parameterlisten innerhalb dieser heterogenen Menge vergleichbar sind, wird davon ausgegangen, dass eine Elementfunktion einen zusätzlichen Parameter hat, den impliziten Objektparameter, der das Objekt darstellt, für das die Elementfunktion aufgerufen wurde . [...]

p3 Ebenso kann der Kontext gegebenenfalls eine Argumentliste erstellen, die ein implizites Objektargument enthält, das das zu bearbeitende Objekt angibt .

Warum müssen wir sogar functionen von Mitgliedern und Nichtmitgliedern vergleichen? Operator überladen, deshalb. Bedenken Sie:

 struct foo{ foo& operator< <(void*); // implementation unimportant }; foo& operator<<(foo&, char const*); // implementation unimportant 

Sie möchten sicher, dass das Folgende die kostenlose function aufruft, oder?

 char const* s = "free foo!\n"; foo f; f < < s; 

Deshalb sind Member- und Nicht-Member-functionen in dem sogenannten Overload-Set enthalten. Um die Auflösung zu vereinfachen, existiert der fettgedruckte Teil des Standardzitats. Außerdem ist dies für uns ein wichtiger Teil (gleiche Klausel):

p4 Für nicht statische Elementfunktionen ist der Typ des impliziten Objektparameters

  • "Lvalue reference to cv X " für functionen, die ohne ref-Qualifikationsmerkmal oder mit dem & ref-Qualifikationsmerkmal deklariert wurden

  • "Rvalue reference to cv X " für functionen, die mit dem && ref-Qualifikationsmerkmal deklariert wurden

Dabei ist X die class, deren function ein Member ist, und cv die cv-Qualifizierung für die Memberfunktionsdeklaration. [...]

p5 Bei der Überladungsauflösung [...] [t] behält der implizite Objektparameter seine [...] Identität, da Konvertierungen des entsprechenden Arguments folgenden zusätzlichen Regeln genügen müssen:

  • kein temporäres Objekt kann eingeführt werden, um das Argument für den impliziten Objektparameter zu halten; und

  • Es können keine benutzerdefinierten Konvertierungen angewendet werden, um eine Übereinstimmung mit dem Typ zu erzielen

[...]

(Das letzte Bit bedeutet nur, dass Sie die Überladungsauflösung auf der Basis impliziter Konvertierungen des Objekts, an dem eine Elementfunktion (oder ein Operator) aufgerufen wird, nicht schummeln können.)

Nehmen wir das erste Beispiel oben in diesem Beitrag. Nach der oben genannten Umwandlung sieht die Überladungsmenge in etwa so aus:

 void f1(test&); // will only match lvalues, linked to 'void test::f() &' void f2(test&&); // will only match rvalues, linked to 'void test::f() &&' 

Dann wird die Argumentliste, die ein impliziertes Objektargument enthält , mit der Parameterliste jeder function abgeglichen, die in der Überladungsmenge enthalten ist. In unserem Fall enthält die Argumentliste nur dieses Objektargument. Mal sehen, wie das aussieht:

 // first call to 'f' in 'main' test t; f1(t); // 't' (lvalue) can match 'test&' (lvalue reference) // kept in overload-set f2(t); // 't' not an rvalue, can't match 'test&&' (rvalue reference) // taken out of overload-set 

Wenn, nachdem alle Überladungen im Satz getestet wurden, nur noch einer übrig ist, ist die Überladungsauflösung erfolgreich und die mit dieser transformierten Überlast verknüpfte function wird aufgerufen. Dasselbe gilt für den zweiten Aufruf an "f":

 // second call to 'f' in 'main' f1(test()); // 'test()' not an lvalue, can't match 'test&' (lvalue reference) // taken out of overload-set f2(test()); // 'test()' (rvalue) can match 'test&&' (rvalue reference) // kept in overload-set 

Beachten Sie jedoch, dass, wenn wir kein Ref-Qualifikationsmerkmal angegeben hätten (und als solches die function nicht überladen hätten), f1 einem rvalue entsprechen würde (immer noch §13.3.1 ):

p5 [...] Für nicht-statische Member-functionen, die ohne Ref-Qualifier deklariert sind, gilt eine zusätzliche Regel:

  • Selbst wenn der implizite Objektparameter nicht const -qualifiziert ist, kann ein rvalue an den Parameter gebunden werden, solange das Argument im übrigen in den Typ des impliziten Objektparameters konvertiert werden kann.
 struct test{ void f() { std::cout < < "lvalue or rvalue object\n"; } }; int main(){ test t; tf(); // OK test().f(); // OK too } 

Nun, warum ist @ Nicols Antwort zumindest teilweise falsch? Er sagt:

Beachten Sie, dass diese Deklaration den Typ von *this ändert.

Das ist falsch, *this ist immer ein lvalue:

§5.3.1 [expr.unary.op] p1

Der unäre * Operator führt eine Indirektion durch : Der Ausdruck, auf den er angewendet wird, soll ein pointers auf einen Objekttyp oder ein pointers auf einen functionstyp sein, und das Ergebnis ist ein L-Wert , der auf das Objekt oder die function verweist, auf die der Ausdruck zeigt.

§9.3.2 [class.this] p1

Im Rumpf einer nicht statischen (9.3) Elementfunktion ist das Schlüsselwort ein prvalue-Ausdruck, dessen Wert die Adresse des Objekts ist, für das die function aufgerufen wird. Der Typ von this in einer Mitgliedsfunktion einer class X ist X* . [...]

Es gibt einen zusätzlichen Anwendungsfall für das Formular lvalue ref-qualifier. C ++ 98 verfügt über eine Sprache, in der nicht konstante Elementfunktionen für classninstanzen mit rvalues ​​aufgerufen werden können. Dies führt zu allen Arten von Seltsamkeit, die gegen das Konzept der Rvalueeness ist und davon abweicht, wie eingebaute Typen funktionieren:

 struct S { S& operator ++(); S* operator &(); }; S() = S(); // rvalue as a left-hand-side of assignment! S& foo = ++S(); // oops, dangling reference &S(); // taking address of rvalue... 

Lvalue Ref-Qualifiers lösen diese Probleme:

 struct S { S& operator ++() &; S* operator &() &; const S& operator =(const S&) &; }; 

Jetzt arbeiten die Operatoren wie die der eingebauten Typen und akzeptieren nur lvalues.

Nehmen wir an, Sie haben zwei functionen in einer class, beide mit demselben Namen und derselben Signatur. Aber einer von ihnen ist const :

 void SomeFunc() const; void SomeFunc(); 

Wenn eine classninstanz nicht const , wählt die Überladungsauflösung vorzugsweise die nicht-konstante Version. Wenn die Instanz const , kann der Benutzer nur die const Version aufrufen. Und der this pointers ist ein const pointers, so dass die Instanz nicht geändert werden kann.

Was “r-value referenz for this” macht, erlaubt Ihnen, eine weitere Alternative hinzuzufügen:

 void RValueFunc() &&; 

Dies ermöglicht Ihnen, eine function zu haben, die nur aufgerufen werden kann, wenn der Benutzer sie über einen geeigneten r-Wert aufruft. Also wenn dies im Typ Object :

 Object foo; foo.RValueFunc(); //error: no `RValueFunc` version exists that takes `this` as l-value. Object().RValueFunc(); //calls the non-const, && version. 

Auf diese Weise können Sie das Verhalten darauf spezialisieren, ob auf das Objekt über einen r-Wert zugegriffen wird oder nicht.

Beachten Sie, dass Sie nicht zwischen den r-Wert-Referenzversionen und den Nicht-Referenzversionen überladen dürfen. Das heißt, wenn Sie einen Mitgliedsfunktionsnamen haben, verwenden alle seine Versionen entweder die l / r-Wert-Qualifikationsmerkmale oder keine davon. Du kannst das nicht tun:

 void SomeFunc(); void SomeFunc() &&; 

Du musst das tun:

 void SomeFunc() &; void SomeFunc() &&; 

Beachten Sie, dass diese Deklaration den Typ von *this ändert. Dies bedeutet, dass die && Versionen alle Mitglieder als r-Wert-Referenzen zugreifen. So wird es möglich, sich leicht innerhalb des Objekts zu bewegen. Das Beispiel, das in der ersten Version des Vorschlags gegeben wurde, ist (Anmerkung: das Folgende ist vielleicht nicht korrekt mit der endgültigen Version von C ++ 11; es ist direkt von dem ursprünglichen “r-Wert von diesem” Vorschlag):

 class X { std::vector data_; public: // ... std::vector const & data() const & { return data_; } std::vector && data() && { return data_; } }; X f(); // ... X x; std::vector a = x.data(); // copy std::vector b = f().data(); // move