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*
. [...]