Erstens ist "ref-qualifiers for * this" nur eine "Marketing-Aussage". Die Art von *this
ändert sich nie, siehe unten in diesem Beitrag. Mit dieser Formulierung ist es jedoch viel einfacher zu verstehen.
Als nächstes wählt der folgende Code die aufzurufende Funktion basierend auf dem Ref-Qualifier des "impliziten Objektparameters" der Funktion † aus :
// 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 durchgeführt, damit Sie die Tatsache ausnutzen können, dass das Objekt, für das die Funktion aufgerufen wird, ein r-Wert ist (z. B. unbenannt temporär). Nehmen Sie den folgenden Code als weiteres Beispiel:
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);
}
};
Dies mag ein bisschen erfunden sein, aber Sie sollten auf die Idee kommen.
Beachten Sie, dass Sie die kombinieren können cv-Qualifier ( const
und volatile
) und ref-Qualifikations ( &
und &&
).
Hinweis: Viele Standardzitate und Erklärungen zur Überlastungsauflösung finden Sie hier!
† Um zu verstehen, wie dies 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 die Antwort von @ Nicol falsch ist, befindet sich unten, wenn Sie es sind nur daran interessiert).
Welche Funktion aufgerufen werden soll, wird durch einen Prozess namens Überlastauflösung bestimmt . Dieser Prozess ist ziemlich kompliziert, daher werden wir nur das Bit berühren, das für uns wichtig ist.
Zunächst ist es wichtig zu sehen, wie die Überlastungsauflösung für Mitgliedsfunktionen funktioniert:
§13.3.1 [over.match.funcs]
p2 Der Satz von Kandidatenfunktionen kann sowohl Element- als auch Nichtmitgliedsfunktionen enthalten, die anhand derselben Argumentliste aufgelöst werden sollen. Damit 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 In ähnlicher Weise kann der Kontext gegebenenfalls eine Argumentliste erstellen , die ein implizites Objektargument enthält , um das zu bearbeitende Objekt zu bezeichnen.
Warum müssen wir überhaupt Funktionen von Mitgliedern und Nichtmitgliedern vergleichen? Überlastung des Bedieners, deshalb. Bedenken Sie:
struct foo{
foo& operator<<(void*); // implementation unimportant
};
foo& operator<<(foo&, char const*); // implementation unimportant
Sie möchten sicher, dass Folgendes die freie Funktion aufruft, nicht wahr?
char const* s = "free foo!\n";
foo f;
f << s;
Aus diesem Grund sind Member- und Non-Member-Funktionen im sogenannten Overload-Set enthalten. Um die Auflösung zu vereinfachen, ist der fett gedruckte Teil des Standardzitats vorhanden. Darüber hinaus ist dies das wichtige Bit für uns (gleiche Klausel):
p4 Für nicht statische Elementfunktionen lautet der Typ des impliziten Objektparameters
"Wertreferenz auf Lebenslauf X
" für Funktionen, die ohne Ref-Qualifier oder mit Ref-Qualifier deklariert wurden&
"R-Wert-Referenz auf Lebenslauf X
" für Funktionen, die mit dem Ref-Qualifier deklariert wurden&&
Wo X
ist die Klasse, zu der die Funktion gehört, und cv ist die cv-Qualifikation in der Deklaration der Mitgliedsfunktion. [...]
p5 Während der Überlastauflösung [...] [t] behält der implizite Objektparameter [...] seine Identität, da Konvertierungen für das entsprechende Argument diese zusätzlichen Regeln befolgen müssen:
Es kann kein temporäres Objekt eingeführt werden, das das Argument für den impliziten Objektparameter enthält. und
Es können keine benutzerdefinierten Konvertierungen angewendet werden, um eine Typübereinstimmung zu erzielen
[...]
(Das letzte Bit bedeutet nur, dass Sie die Überlastungsauflösung nicht aufgrund impliziter Konvertierungen des Objekts betrügen können, für das eine Mitgliedsfunktion (oder ein Operator) aufgerufen wird.)
Nehmen wir das erste Beispiel oben in diesem Beitrag. Nach der oben genannten Transformation sieht der Überlastungssatz ungefähr 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 implizites Objektargument enthält , mit der Parameterliste jeder in der Überladungsmenge 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(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 nach dem Testen aller Überlastungen im Satz nur noch eine übrig bleibt, war die Überlastungsauflösung erfolgreich und die mit dieser transformierten Überlast verknüpfte Funktion wird aufgerufen. Gleiches 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, wenn wir kein Ref-Qualifikationsmerkmal bereitgestellt hätten (und als solches die Funktion nicht überladen hätten), dies (noch ) mit einem r-Wert übereinstimmen f1
würde§13.3.1
:
p5 [...] Für nicht statische Elementfunktionen, die ohne Ref-Qualifier deklariert wurden , gilt eine zusätzliche Regel:
- Selbst wenn der implizite Objektparameter nicht
const
qualifiziert ist, kann ein r-Wert 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;
t.f(); // OK
test().f(); // OK too
}
Nun, warum @ Nicol's Antwort zumindest teilweise falsch ist. Er sagt:
Beachten Sie, dass diese Deklaration den Typ von ändert *this
.
Das ist falsch, *this
ist immer ein Wert:
§5.3.1 [expr.unary.op] p1
Der unäre *
Operator führt eine Indirektion durch : Der Ausdruck, auf den er angewendet wird, muss ein Zeiger auf einen Objekttyp oder ein Zeiger auf einen Funktionstyp sein, und das Ergebnis ist ein Wert, der sich auf das Objekt oder die Funktion bezieht, auf die der Ausdruck zeigt.
§9.3.2 [class.this] p1
Im Hauptteil einer nicht statischen (9.3) Mitgliedsfunktion ist das Schlüsselwort this
ein prvalue-Ausdruck, dessen Wert die Adresse des Objekts ist, für das die Funktion aufgerufen wird. Der Typ this
in einer Member-Funktion einer Klasse X
ist X*
. [...]
MyType(int a, double b) &&
?Es gibt einen zusätzlichen Anwendungsfall für das lvalue ref-qualifier-Formular. C ++ 98 verfügt über eine Sprache, mit der
const
Nichtmitgliedsfunktionen für Klasseninstanzen aufgerufen werden können, bei denen es sich um r-Werte handelt. Dies führt zu allen Arten von Verrücktheit, die gegen das Konzept der Wertigkeit verstoßen und von der Funktionsweise eingebauter Typen abweichen:Lvalue Ref-Qualifikanten lösen diese Probleme:
Jetzt arbeiten die Operatoren wie die der eingebauten Typen und akzeptieren nur l-Werte.
quelle
Angenommen, Sie haben zwei Funktionen für eine Klasse, beide mit demselben Namen und derselben Signatur. Aber einer von ihnen wird erklärt
const
:Wenn dies keine Klasseninstanz ist
const
, wählt die Überlastungsauflösung vorzugsweise die nicht konstante Version aus. Wenn dies der Fall istconst
, kann der Benutzer nur dieconst
Version aufrufen . Und derthis
Zeiger ist einconst
Zeiger, daher kann die Instanz nicht geändert werden.Mit "r-Wert-Referenz dafür" können Sie eine weitere Alternative hinzufügen:
Auf diese Weise können Sie eine Funktion haben, die nur aufgerufen werden kann, wenn der Benutzer sie über einen geeigneten r-Wert aufruft. Also, wenn dies in der Art ist
Object
: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 Versionen entweder die l / r-Wert-Qualifikationsmerkmale für
this
oder keine von ihnen. Das kannst du nicht machen:Du musst das tun:
Beachten Sie, dass diese Deklaration den Typ von ändert
*this
. Dies bedeutet, dass&&
alle Versionen auf Mitglieder als R-Wert-Referenzen zugreifen. So wird es möglich, sich leicht aus dem Objekt heraus 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 ergibt sich direkt aus dem ursprünglichen "r-Wert aus diesem" Vorschlag):quelle
std::move
die zweite Version, nicht? Warum gibt die r-Wert-Referenz auch zurück?*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 das Objekt "this" (hier absichtlich angegeben!) Während der Überlastungsauflösung und des Funktionsaufrufs gebunden ist. Also keine Änderung,*this
da dies behoben ist, wie Xeo erklärt. Ändern Sie stattdessen den Parameter "hiddden", um ihn auf lvalue- oder rvalue-reference zu setzen, genau wie es derconst
Funktionsqualifiziererconst
usw. macht .