Was ist “rvalue reference for *this”?

Lesezeit: 10 Minuten

Was ist rvalue reference for this
Ryaner

Stieß auf einen Vorschlag namens “rvalue reference for *this” in clang’s C++11-Statusseite.

Ich habe ziemlich viel über rvalue-Referenzen gelesen und sie verstanden, aber ich glaube nicht, dass ich darüber Bescheid weiß. Ich konnte auch nicht viele Ressourcen im Internet finden, die die Begriffe verwenden.

Es gibt einen Link zum Vorschlagspapier auf der Seite: N2439 (Erweitern der Move-Semantik auf *this), aber ich bekomme auch nicht viele Beispiele von dort.

Worum geht es bei dieser Funktion?

1647198014 657 Was ist rvalue reference for this
Xeo

Erstens ist „ref-qualifiers for *this“ nur eine „Marketingaussage“. Die Art von *this ändert sich nie, siehe unten in diesem Beitrag. Es ist jedoch viel einfacher, es mit dieser Formulierung zu verstehen.

Als Nächstes wählt der folgende Code die aufzurufende Funktion basierend auf aus Ref-Qualifizierer des “impliziten Objektparameters” der Funktion:

// t.cpp
#include <iostream>

struct test{
  void f() &{ std::cout << "lvalue object\n"; }
  void f() &&{ std::cout << "rvalue object\n"; }
};

int main(){
  test t;
  t.f(); // lvalue
  test().f(); // rvalue
}

Ausgabe:

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

Das Ganze wird gemacht, damit Sie die Tatsache ausnutzen können, wenn das Objekt, für das die Funktion aufgerufen wird, ein Rvalue ist (z. B. ein unbenanntes temporäres Objekt). Nehmen Sie als weiteres Beispiel folgenden Code:

struct test2{
  std::unique_ptr<int[]> heavy_resource;

  test2()
    : heavy_resource(new int[500]) {}

  operator std::unique_ptr<int[]>() const&{
    // lvalue object, deep copy
    std::unique_ptr<int[]> p(new int[500]);
    for(int i=0; i < 500; ++i)
      p[i] = heavy_resource[i];

    return p;
  }

  operator std::unique_ptr<int[]>() &&{
    // rvalue object
    // we are garbage anyways, just move resource
    return std::move(heavy_resource);
  }
};

Das mag ein bisschen erfunden sein, aber Sie sollten die Idee bekommen.

Beachten Sie, dass Sie die kombinieren können Lebenslauf-Qualifikanten (const und volatile) und Ref-Qualifikanten (& und &&).


Hinweis: Viele Standardzitate und Erklärungen zur Auflösung der Überlastung 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 mit dem C++-Standard befassen (der Teil, der erklärt, warum @Nicols Antwort falsch ist, befindet sich ganz unten, wenn Sie es sind nur daran interessiert).

Welche Funktion aufgerufen wird, wird durch einen aufgerufenen Prozess bestimmt Überlastungsauflösung. Dieser Prozess ist ziemlich kompliziert, also berühren wir nur das Bit, das für uns wichtig ist.

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

§13.3.1 [over.match.funcs]

p2 Der Satz von Kandidatenfunktionen kann sowohl Mitglieds- als auch Nichtmitgliedsfunktionen enthalten, die gegen dieselbe Argumentliste aufgelöst werden sollen. Damit Argument- und Parameterlisten innerhalb dieser heterogenen Menge vergleichbar sind, Es wird davon ausgegangen, dass eine Memberfunktion einen zusätzlichen Parameter hat, der als impliziter Objektparameter bezeichnet wird und das Objekt darstellt, für das die Memberfunktion aufgerufen wurde. […]

p3 In ähnlicher Weise kann der Kontext gegebenenfalls eine Argumentliste erstellen, die ein enthält implizites Objektargument um das zu operierende Objekt zu bezeichnen.

Warum müssen wir überhaupt Member- und Nicht-Member-Funktionen vergleichen? Operatorüberladung, deshalb. Bedenken Sie:

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

foo& operator<<(foo&, char const*); // implementation unimportant

Sie möchten sicherlich, dass Folgendes die kostenlose Funktion aufruft, nicht wahr?

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

Deshalb sind Member- und Nicht-Member-Funktionen im sogenannten Overload-Set enthalten. Um die Auflösung weniger kompliziert zu machen, existiert der fettgedruckte Teil des Standardzitats. Außerdem ist dies der wichtige Teil für uns (gleiche Klausel):

p4 Bei nicht statischen Elementfunktionen ist der Typ des impliziten Objektparameters

  • „lvalue Verweis auf Lebenslauf X” für ohne a deklarierte Funktionen Ref-Qualifizierer oder mit dem & Ref-Qualifizierer

  • „rvalue Verweis auf Lebenslauf X” für mit deklarierte Funktionen && Ref-Qualifizierer

wo X ist die Klasse, in der die Funktion Mitglied ist, und Lebenslauf ist die CV-Qualifikation in der Member-Funktionsdeklaration. […]

p5 Während Überlastauflösung […]

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

  • Es können keine benutzerdefinierten Konvertierungen angewendet werden, um eine Typübereinstimmung damit zu erzielen

[…]

(Das letzte Bit bedeutet nur, dass Sie die Überladungsauflösung nicht basierend auf impliziten Konvertierungen des Objekts, für das eine Memberfunktion (oder ein Operator) aufgerufen wird, betrügen können.)

Nehmen wir das erste Beispiel oben in diesem Beitrag. Nach der oben genannten Transformation sieht das Überladungsset 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 die Argumentliste, die eine enthält implizites Objektargument, wird mit der Parameterliste jeder im Überladungssatz enthaltenen Funktion abgeglichen. In unserem Fall enthält die Argumentliste nur dieses Objektargument. Mal sehen, wie das aussieht:

// first call to 'f' in 'main'
test t;
f1
       // kept in overload-set
f2
       // taken out of overload-set

Wenn nach dem Testen aller Überladungen im Satz nur noch eine verbleibt, war die Überladungsauflösung erfolgreich und die mit dieser transformierten Überladung verknüpfte Funktion wird aufgerufen. Dasselbe gilt für den zweiten Aufruf von ‘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 wir keine bereitgestellt hatten Ref-Qualifizierer (und damit die Funktion nicht überladen), dass f1 möchten Übereinstimmung mit einem rvalue (still §13.3.1):

p5 […] Für nicht statische Elementfunktionen, die ohne a deklariert sind Ref-Qualifizierergilt eine zusätzliche Regel:

  • auch wenn der implizite Objektparameter dies nicht ist const-qualifiziert, kann ein rvalue an den Parameter gebunden werden, solange das Argument ansonsten in den Typ des impliziten Objektparameters konvertiert werden kann.
struct test{
  void f() { std::cout << "lvalue or rvalue object\n"; }
};

int main(){
  test t;
  t.f(); // OK
  test().f(); // OK too
}

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

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

Das ist falsch, *this ist immer ein lvalue:

§5.3.1 [expr.unary.op] p1

Das Unäre * Betreiber durchführt indirekt: Der Ausdruck, auf den es angewendet wird, soll ein Zeiger auf einen Objekttyp oder ein Zeiger auf einen Funktionstyp sein und das Ergebnis ist ein lvalue bezieht sich auf das Objekt oder die Funktion, auf die der Ausdruck zeigt.

§9.3.2 [class.this] p1

Im Hauptteil einer nicht statischen (9.3) Elementfunktion wird das Schlüsselwort this ist ein Prvalue-Ausdruck, dessen Wert die Adresse des Objekts ist, für das die Funktion aufgerufen wird. Die Art von this in einer Member-Funktion einer Klasse X ist X*. […]

  • Ich glaube, die Paraneter-Typen direkt nach dem Abschnitt “nach der Transformation” sollten “foo” anstelle von “test” sein.

    – Ryaner

    25. Dezember 2011 um 5:04 Uhr

  • @ryaner: Guter Fund, danke. Allerdings ist nicht der Parameter, sondern die Klassenkennung der Funktionen falsch. 🙂

    – Xeo

    25. Dezember 2011 um 8:50 Uhr


  • Ups, Entschuldigung, ich habe die Spielzeugklasse namens Test vergessen, als ich diesen Teil gelesen habe, und dachte, f ist in foo enthalten, also mein Kommentar.

    – Ryaner

    25. Dezember 2011 um 18:05 Uhr

  • Kann man das mit Konstruktoren machen: MyType(int a, double b) &&?

    – German Diago

    2. Dezember 2014 um 4:52 Uhr

  • “Die Art von * ändert sich nie” Sie sollten vielleicht etwas klarer machen, dass es sich nicht basierend auf der R / L-Wert-Qualifizierung ändert. aber es kann zwischen const/non-const wechseln.

    – xaxxon

    13. November 2016 um 11:30 Uhr

Es gibt einen zusätzlichen Anwendungsfall für das Lvalue-Ref-Qualifikator-Formular. C++98 hat eine Sprache, die nicht-const Mitgliedsfunktionen, die für Klasseninstanzen aufgerufen werden sollen, die Rvalues ​​sind. Dies führt zu allerlei Seltsamkeiten, die gegen das eigentliche Konzept der Rvalueness verstoßen und von der Funktionsweise eingebauter Typen abweichen:

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-Qualifizierer lösen diese Probleme:

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

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

1647198015 809 Was ist rvalue reference for this
Nicol Bola

Angenommen, Sie haben zwei Funktionen in einer Klasse, beide mit demselben Namen und derselben Signatur. Aber einer von ihnen ist deklariert const:

void SomeFunc() const;
void SomeFunc();

Wenn eine Klasseninstanz nicht const, wird die Überladungsauflösung vorzugsweise die nicht-konstante Version auswählen. Wenn die Instanz ist constder Benutzer kann nur die aufrufen const Ausführung. Und das this Zeiger ist ein const Zeiger, sodass die Instanz nicht geändert werden kann.

Was “r-value reference for this” bewirkt, ist, dass Sie eine weitere Alternative hinzufügen können:

void RValueFunc() &&;

Dies ermöglicht Ihnen, eine Funktion zu haben, die kann nur aufgerufen werden, wenn der Benutzer sie über einen geeigneten R-Wert aufruft. Also wenn das im Typ ist 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 zwischen den R-Wert-Referenzversionen und den Nicht-Referenzversionen nicht überladen dürfen. Das heißt, wenn Sie einen Mitgliedsfunktionsnamen haben, verwenden alle seine Versionen entweder die l/r-Wert-Qualifizierer on this, oder keiner von ihnen tut. Das geht nicht:

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

Du musst das tun:

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

Beachten Sie, dass diese Deklaration den Typ von ändert *this. Dies bedeutet, dass die && versioniert alle Zugriffsmitglieder als R-Wert-Referenzen. So wird es möglich, sich einfach innerhalb des Objekts zu bewegen. Das in der ersten Version des Vorschlags angegebene Beispiel lautet (Hinweis: Folgendes ist möglicherweise mit der endgültigen Version von C++ 11 nicht korrekt; es stammt direkt aus dem ursprünglichen „r-value from this“-Vorschlag):

class X {
   std::vector<char> data_;
public:
   // ...
   std::vector<char> const & data() const & { return data_; }
   std::vector<char> && data() && { return data_; }
};

X f();

// ...
X x;
std::vector<char> a = x.data(); // copy
std::vector<char> b = f().data(); // move

  • Ich denke, das müssen Sie std::move die zweite Version, nicht? Warum wird auch die Rvalue-Referenz zurückgegeben?

    – Xeo

    22. Dezember 2011 um 23:17 Uhr

  • @Xeo: Weil das Beispiel im Vorschlag so war; Keine Ahnung, ob es mit der aktuellen Version noch funktioniert. Und der Grund für die R-Wert-Referenzrückgabe ist, dass die Bewegung der Person überlassen werden sollte, die sie erfasst. Es sollte noch nicht passieren, nur für den Fall, dass er es tatsächlich in einem && statt einem Wert speichern möchte.

    – Nicol Bolas

    22. Dezember 2011 um 23:20 Uhr

  • Richtig, ich dachte irgendwie an den Grund für meine zweite Frage. Ich frage mich jedoch, verlängert ein rvalue-Verweis auf das Mitglied einer temporären Person die Lebensdauer dieser temporären Person oder des Mitglieds davon? Ich könnte schwören, dass ich vor einiger Zeit eine Frage dazu auf SO gesehen habe …

    – Xeo

    22. Dezember 2011 um 23:22 Uhr

  • @Xeo: Das stimmt nicht ganz. Die Überladungsauflösung wählt immer die nicht-konstante Version aus, falls vorhanden. Sie müssten eine Umwandlung durchführen, um die const-Version zu erhalten. Ich habe den Beitrag zur Klarstellung aktualisiert.

    – Nicol Bolas

    23. Dezember 2011 um 18:04 Uhr

  • Ich dachte, ich könnte es erklären, schließlich habe ich dieses Feature für C++11 erstellt 😉 Xeo besteht zu Recht darauf, dass es den Typ von nicht ändert *this, aber ich kann verstehen, woher die Verwirrung kommt. Dies liegt daran, dass ref-qualifier den Typ des impliziten (oder „versteckten“) Funktionsparameters ändert, an den „dieses“ (hier absichtlich in Anführungszeichen gesetzte) Objekt während der Überladungsauflösung und des Funktionsaufrufs gebunden ist. Also keine Änderung *this da dies behoben ist, wie Xeo erklärt. Ändern Sie stattdessen den “versteckten” Parameter, um ihn zu einer lvalue- oder rvalue-Referenz zu machen, genau wie const Funktionsqualifizierer macht es const etc..

    – bronek

    1. Februar 2012 um 22:28 Uhr


998760cookie-checkWas ist “rvalue reference for *this”?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy