Po pierwsze, „kwalifikatory odniesienia dla * tego” to tylko „oświadczenie marketingowe”. Rodzaj *this
nigdy się nie zmienia, patrz na dole tego postu. Jednak dzięki temu sformułowaniu o wiele łatwiej to zrozumieć.
Następnie poniższy kod wybiera funkcję, która ma zostać wywołana na podstawie kwalifikatora odniesienia „niejawnego parametru obiektu” funkcji † :
// 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
}
Wynik:
$ clang++ -std=c++0x -stdlib=libc++ -Wall -pedantic t.cpp
$ ./a.out
lvalue object
rvalue object
Wszystko po to, abyś mógł skorzystać z faktu, że obiekt, w którym funkcja jest wywoływana, to wartość (na przykład nienazwana tymczasowa). Weź następujący kod jako kolejny przykład:
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);
}
};
To może być nieco wymyślone, ale powinieneś o tym pomyśleć.
Pamiętaj, że możesz łączyć kwalifikatory cv ( const
i volatile
) i kwalifikatory ref ( &
i &&
).
Uwaga: tutaj znajduje się wiele standardowych cytatów i wyjaśnienie rozwiązania problemu przeciążenia!
† Aby zrozumieć, jak to działa i dlaczego odpowiedź @Nicol Bolas jest co najmniej częściowo błędna, musimy nieco zagłębić się w standard C ++ (część wyjaśniająca, dlaczego odpowiedź @ Nicol jest błędna, znajduje się na dole, jeśli jesteś tylko zainteresowani tym).
Która funkcja ma zostać wywołana, zależy od procesu zwanego rozdzielczością przeciążenia . Ten proces jest dość skomplikowany, więc dotkniemy tylko tego, co jest dla nas ważne.
Po pierwsze, ważne jest, aby zobaczyć, jak działa rozwiązywanie przeciążenia dla funkcji członka:
§13.3.1 [over.match.funcs]
p2 Zbiór funkcji kandydujących może zawierać zarówno funkcje składowe, jak i nie będące członkami, które zostaną rozstrzygnięte na tej samej liście argumentów. Aby listy argumentów i parametrów były porównywalne w ramach tego heterogenicznego zestawu, uważa się, że funkcja elementu zawiera dodatkowy parametr, zwany niejawnym parametrem obiektu, który reprezentuje obiekt, dla którego funkcja elementu została wywołana . [...]
p3 Podobnie, gdy jest to właściwe, kontekst może skonstruować listę argumentów, która zawiera domyślny argument obiektowy dla oznaczenia obiektu, który ma być obsługiwany.
Dlaczego musimy nawet porównywać funkcje składowe i nie będące członkami? Przeciążenie operatora, dlatego. Rozważ to:
struct foo{
foo& operator<<(void*); // implementation unimportant
};
foo& operator<<(foo&, char const*); // implementation unimportant
Na pewno chcesz, aby następujące wywołały funkcję bezpłatną, prawda?
char const* s = "free foo!\n";
foo f;
f << s;
Dlatego funkcje składowe i nie będące członkami są zawarte w tak zwanym zestawie przeciążenia. Aby uprościć rozdzielczość, istnieje odważna część standardowej oferty. Ponadto jest to dla nas ważny bit (ta sama klauzula):
p4 W przypadku niestatycznych funkcji składowych typem niejawnego parametru obiektu jest
gdzie X
jest klasą, której funkcja jest składnikiem, a cv jest kwalifikacją cv w deklaracji funkcji składowej. [...]
p5 Podczas rozwiązywania przeciążenia [...] [t] domyślny parametr obiektu [...] zachowuje swoją tożsamość, ponieważ konwersje na odpowiedni argument powinny być zgodne z następującymi dodatkowymi zasadami:
nie można wprowadzić żadnego obiektu tymczasowego do przechowywania argumentu dla parametru obiektu niejawnego; i
żadne konwersje zdefiniowane przez użytkownika nie mogą być zastosowane, aby osiągnąć dopasowanie typu do niego
[...]
(Ostatni bit oznacza po prostu, że nie można oszukiwać rozdzielczości przeciążenia na podstawie niejawnej konwersji obiektu, do którego wywoływana jest funkcja członka (lub operator).)
Weźmy pierwszy przykład na początku tego postu. Po wspomnianej transformacji zestaw przeciążeń wygląda mniej więcej tak:
void f1(test&); // will only match lvalues, linked to 'void test::f() &'
void f2(test&&); // will only match rvalues, linked to 'void test::f() &&'
Następnie lista argumentów zawierająca domyślny argument obiektowy jest porównywana z listą parametrów każdej funkcji zawartej w zestawie przeciążenia. W naszym przypadku lista argumentów będzie zawierać tylko ten argument obiektowy. Zobaczmy, jak to wygląda:
// 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
Jeśli po przetestowaniu wszystkich przeciążeń w zestawie pozostanie tylko jeden, rozwiązywanie problemu przeciążenia zakończyło się powodzeniem i wywoływana jest funkcja powiązana z tym transformowanym przeciążeniem. To samo dotyczy drugiego połączenia z „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
Uwaga jednak, że gdyby nie my dostarczyły żadnych Ref-kwalifikator (i jako takie nie przeciążony funkcji), że f1
byłoby dopasować rvalue (jeszcze §13.3.1
):
p5 [...] W przypadku niestatycznych funkcji składowych zadeklarowanych bez kwalifikatora odniesienia obowiązuje dodatkowa reguła:
- nawet jeśli niejawny parametr obiektu nie
const
jest kwalifikowany, wartość można powiązać z parametrem, o ile pod wszystkimi innymi względami argument można przekonwertować na typ parametru obiektu niejawnego.
struct test{
void f() { std::cout << "lvalue or rvalue object\n"; }
};
int main(){
test t;
t.f(); // OK
test().f(); // OK too
}
Teraz, dlaczego odpowiedź @ Nicol jest przynajmniej częściowo błędna. On mówi:
Uwaga: ta deklaracja zmienia typ *this
.
To źle, zawsze*this
jest to wartość:
§5.3.1 [expr.unary.op] p1
Jednoargumentowy *
operator wykonuje pośrednie : wyrażenie, do którego jest stosowane, powinno być wskaźnikiem do typu obiektu lub wskaźnikiem do typu funkcji, a wynikiem jest wartość odnosząca się do obiektu lub funkcji, na którą wskazuje wyrażenie.
§9.3.2 [class.this] p1
W treści niestatystycznej (9,3) funkcji this
składowej słowo kluczowe jest wyrażeniem prvalue, którego wartością jest adres obiektu, dla którego funkcja jest wywoływana. Typem this
funkcji składowej klasy X
jest X*
. [...]
MyType(int a, double b) &&
:?Istnieje dodatkowy przypadek użycia formularza kwalifikatora refinansowania wartości. C ++ 98 ma język, który umożliwia
const
wywoływanie funkcji nie będących członkami dla instancji klas, które są wartościami. Prowadzi to do wszelkiego rodzaju dziwności, która jest sprzeczna z samą koncepcją wartościowości i odbiega od sposobu działania typów wbudowanych:Kwalifikatory referencyjne Lvalue rozwiązują następujące problemy:
Teraz operatory działają jak te wbudowanych typów, akceptując tylko wartości lv.
źródło
Załóżmy, że masz dwie funkcje w klasie, obie o tej samej nazwie i podpisie. Ale jeden z nich jest zadeklarowany
const
:Jeśli instancja klasy nie jest
const
, rozdzielczość przeciążenia będzie preferencyjnie wybierać wersję inną niż const. Jeśli instancja jestconst
, użytkownik może wywołać tylkoconst
wersję. Athis
wskaźnik toconst
wskaźnik, więc instancja nie może być zmieniony.To, co robi „wartość odniesienia r dla tego”, pozwala dodać inną alternatywę:
Pozwala to na posiadanie funkcji, którą można wywołać tylko wtedy, gdy użytkownik wywoła ją za pomocą odpowiedniej wartości r. Więc jeśli jest to typ
Object
:W ten sposób możesz specjalizować zachowanie w oparciu o to, czy dostęp do obiektu jest uzyskiwany za pomocą wartości r, czy nie.
Pamiętaj, że nie możesz przeciążać wersji referencyjnych o wartości r i wersji innych niż referencyjne. To znaczy, jeśli masz nazwę funkcji składowej, wszystkie jej wersje albo używają kwalifikatorów wartości l / r
this
, albo żadna z nich nie. Nie możesz tego zrobić:Musisz to zrobić:
Uwaga: ta deklaracja zmienia typ
*this
. Oznacza to, że&&
wszystkie wersje mają dostęp do elementów jako referencje wartości r. Dzięki temu możliwe jest łatwe poruszanie się wewnątrz obiektu. Przykład podany w pierwszej wersji propozycji to (uwaga: poniższe informacje mogą być niepoprawne w ostatecznej wersji C ++ 11; pochodzi bezpośrednio z początkowej propozycji „wartość r z tej”):źródło
std::move
drugiej wersji, nie? Ponadto, dlaczego zwracane jest odwołanie do wartości?*this
, ale rozumiem, skąd bierze się zamieszanie. Wynika to z faktu, że kwalifikator ref zmienia typ niejawnego (lub „ukrytego”) parametru funkcji, z którym związany jest ten „obiekt” (tutaj podano cudzysłowy!) Podczas rozwiązywania przeciążenia i wywołania funkcji. Więc nie ma zmiany,*this
ponieważ jest to naprawione, jak wyjaśnia Xeo. Zamiast tego zmiana parametru „ukrytego”, aby stał się referencją do wartości lvalue lub rvalue, podobnie jakconst
kwalifikator funkcji sprawia, żeconst
itd.