Jeśli czytasz kod, taki jak
auto&& var = foo();
gdzie foo
jest dowolna funkcja zwracająca wartość typu T
. Następnie var
jest lwartość typu rwartość odniesienia do T
. Ale co to oznacza var
? Czy to oznacza, że wolno nam kraść zasoby var
? Czy są jakieś rozsądne sytuacje, w których powinieneś użyć, auto&&
aby powiedzieć czytelnikowi o swoim kodzie, podobnie jak robisz, gdy zwracasz a, unique_ptr<>
aby powiedzieć, że masz wyłączną własność? A co, na przykład, T&&
kiedy T
jest klasowy?
Chcę tylko zrozumieć, czy istnieją inne przypadki użycia auto&&
niż te w programowaniu szablonów; podobnie jak te omówione w przykładach w tym artykule Universal References autorstwa Scotta Meyersa.
auto&&
? Zastanawiałem się nad tym, dlaczego pętla for oparta na zakresie rozszerza się, aby użyć jejauto&&
jako przykładu, ale nie doszedłem do tego. Być może ktokolwiek odpowie, może to wyjaśnić.foo
powrocie, przechowywanie rvalue ref to brzmi jak UB do ne.foo
może być na przykład wyglądać tak:int foo(){return 1;}
.Odpowiedzi:
Używając
auto&& var = <initializer>
you, mówisz: zaakceptuję dowolny inicjalizator, niezależnie od tego, czy jest to wyrażenie l-wartość, czy r-wartość i zachowam jego stałość . Jest to zwykle używane do przekazywania (zwykle zT&&
). Powodem, dla którego to działa, jest to, że „uniwersalne odniesienie”auto&&
lubT&&
będzie wiązać się z czymkolwiek .Możesz powiedzieć, dlaczego nie użyć po prostu znaku,
const auto&
ponieważ to również będzie wiązać się z czymkolwiek? Problem z używaniemconst
referencji polega na tym, że tak jestconst
! Nie będzie można później powiązać go z żadnymi odwołaniami niebędącymi stałymi ani wywoływać funkcji składowych, które nie są oznaczoneconst
.Jako przykład wyobraź sobie, że chcesz uzyskać a
std::vector
, przenieś iterator do jego pierwszego elementu i zmodyfikuj w jakiś sposób wartość wskazywaną przez ten iterator:Ten kod skompiluje się dobrze, niezależnie od wyrażenia inicjatora. Alternatywy, które mogą
auto&&
zawieść w następujący sposób:Więc do tego
auto&&
działa idealnie! Przykładem takiego użyciaauto&&
jestfor
pętla oparta na zakresie . Zobacz moje drugie pytanie, aby uzyskać więcej informacji.Jeśli następnie użyjesz
std::forward
w swoimauto&&
odwołaniu, aby zachować fakt, że pierwotnie była to lwartość lub rwartość, twój kod mówi: Teraz, gdy mam twój obiekt z wyrażenia lwartość lub rwartość, chcę zachować każdą pierwotną wartość tak, żebym mógł z niego korzystać najefektywniej - może to go unieważnić. Jak w:Pozwala
use_it_elsewhere
to wyrwać jego wnętrzności ze względu na wydajność (unikanie kopii), gdy oryginalny inicjator był modyfikowalną rvalue.Co to oznacza, jeśli chodzi o to, czy możemy, lub kiedy możemy kraść zasoby
var
? Cóż, ponieważauto&&
wola wiąże się z czymkolwiek, nie możemy sami próbować wyrwaćvar
wnętrzności - może to być wartość l, a nawet stała. Możemy jednak korzystaćstd::forward
z innych funkcji, które mogą całkowicie spustoszyć jego wnętrze. Gdy tylko to zrobimy, powinniśmy uważać, że jesteśmyvar
w stanie nieważności.Teraz zastosujmy to do przypadku
auto&& var = foo();
, jak podano w pytaniu, gdzie foo zwracaT
wartość a. W takim przypadku wiemy na pewno, że typvar
zostanie wydedukowany jakoT&&
. Ponieważ wiemy na pewno, że jest to wartość r, nie potrzebujemystd::forward
pozwolenia na kradzież jej zasobów. W tym konkretnym przypadku, wiedząc, żefoo
zwraca wartość , czytelnik powinien po prostu odczytać to jako: Biorę odwołanie do wartości r do tymczasowego zwróconego zfoo
, więc mogę z radością odejść od tego.Jako uzupełnienie, myślę, że warto wspomnieć, kiedy
some_expression_that_may_be_rvalue_or_lvalue
może pojawić się wyrażenie takie jak , inne niż sytuacja, w której kod może się zmienić. Oto wymyślony przykład:Oto
get_vector<T>()
cudowne wyrażenie, które może być lwartością lub rwartością w zależności od typu ogólnegoT
. Zasadniczo zmieniamy typ zwracaniaget_vector
przez parametr szablonufoo
.Kiedy wywołujemy
foo<std::vector<int>>
,get_vector
zwróciglobal_vec
wartość, co daje wyrażenie rvalue. Alternatywnie, gdy wywołaszfoo<std::vector<int>&>
,get_vector
zwróciglobal_vec
przez odniesienie, co spowoduje wyrażenie lvalue.Jeśli zrobimy:
Zgodnie z oczekiwaniami otrzymujemy następujący wynik:
Jeśli było zmienić
auto&&
w kodzie, aby którykolwiek zauto
,auto&
,const auto&
czyconst auto&&
wtedy nie będzie uzyskać wynik chcemy.Alternatywnym sposobem zmiany logiki programu w zależności od tego, czy
auto&&
odwołanie jest zainicjowane wyrażeniem l-wartość, czy r-wartość, jest użycie cech typu:źródło
T vec = get_vector<T>();
funkcja foo? A może upraszczam to do absurdu :)Po pierwsze, polecam przeczytanie mojej odpowiedzi jako dodatkowej lektury, aby wyjaśnić krok po kroku, jak działa dedukcja argumentów szablonowych dla odwołań uniwersalnych.
Niekoniecznie. Co się stanie, jeśli
foo()
nagle zwróciłeś odniesienie lub zmieniłeś połączenie, ale zapomniałeś zaktualizować użycievar
? Lub jeśli jesteś w kodzie ogólnym i zwracany typfoo()
może się zmieniać w zależności od parametrów?Pomyśl o tym,
auto&&
że jest dokładnie tym samym, coT&&
intemplate<class T> void f(T&& v);
, ponieważ to (prawie † ) dokładnie to. Co robisz z odwołaniami uniwersalnymi w funkcjach, kiedy musisz je przekazać lub w jakikolwiek sposób wykorzystać? Używasz,std::forward<T>(v)
aby odzyskać pierwotną kategorię wartości. Jeśli przed przekazaniem do funkcji była to lwartość, po przekazaniu pozostaje lwartościąstd::forward
. Jeśli była to wartość r, stanie się ponownie wartością r (pamiętaj, że nazwane odniesienie do rwartości jest lwartością).Jak więc
var
prawidłowo używać w ogólny sposób? Użyjstd::forward<decltype(var)>(var)
. Będzie to działać dokładnie tak samo, jakstd::forward<T>(v)
w powyższym szablonie funkcji. Jeślivar
jest aT&&
, otrzymasz z powrotem wartość r, a jeśli takT&
, otrzymasz z powrotem wartość l.A więc wracając do tematu: Co nam powiedz
auto&& v = f();
istd::forward<decltype(v)>(v)
w bazie kodu? Mówią nam, żev
zostaną pozyskane i przekazane w najbardziej efektywny sposób. Pamiętaj jednak, że po przesłaniu takiej zmiennej jest możliwe, że jest ona przeniesiona, więc byłoby niepoprawne, użyj jej dalej bez resetowania.Osobiście używam
auto&&
w kodzie ogólnym, gdy potrzebuję zmiennej, którą można modyfikować . Doskonałe przekazywanie wartości r jest modyfikacją, ponieważ operacja przenoszenia potencjalnie kradnie jej wnętrzności. Jeśli chcę być po prostu leniwy (tj. Nie przeliterować nazwy typu, nawet jeśli ją znam) i nie muszę modyfikować (np. Podczas drukowania elementów zakresu), będę się trzymaćauto const&
.†
auto
jest tak daleko różne, żeauto v = {1,2,3};
uczyni , podczas gdy będzie awaria odliczenie.v
std::initializer_list
f({1,2,3})
źródło
foo()
zwraca typ wartościT
, tovar
(to wyrażenie) będzie lwartością, a jego typ (tego wyrażenia) będzie odniesieniem do rwartościT
(tjT&&
.).Rozważmy jakiś typ,
T
który ma konstruktora przenoszenia i załóżmyużywa tego konstruktora przenoszenia.
Teraz użyjmy odwołania pośredniego, aby przechwycić wynik z
foo
:wyklucza to użycie konstruktora move, więc wartość zwracana będzie musiała zostać skopiowana zamiast przeniesiona (nawet jeśli użyjemy
std::move
tutaj, nie możemy faktycznie przejść przez const ref)Jeśli jednak używamy
konstruktor przenoszenia jest nadal dostępny.
Aby odpowiedzieć na inne pytania:
Pierwszą rzeczą, jak mówi Xeo, jest zasadniczo przekazanie X tak wydajnie, jak to możliwe , niezależnie od typu X. Tak więc, widząc kod, który używa
auto&&
wewnętrznie, powinno komunikować, że użyje wewnętrznie semantyki przenoszenia tam, gdzie jest to właściwe.Kiedy szablon funkcji przyjmuje argument typu
T&&
, to mówi, że może przesunąć przekazany obiekt. Zwrócenieunique_ptr
jawnie daje własność wywołującemu; przyjmowanieT&&
może usunąć własności z osoby dzwoniącej (jeżeli konstruktor ruch istnieje, itd.).źródło
ref
irvref
to zarówno lwartościami. Jeśli chcesz konstruktora przenoszenia, musisz napisaćT t(std::move(rvref))
.auto const &
:?auto&&
i co powiesz czytelnikowi swojego kodu używającauto&&
?auto &&
Składnia wykorzystuje dwie nowe funkcje C ++ 11:auto
Część kompilator pozwala wydedukować typu na podstawie kontekstu (wartość zwracana w tym przypadku). To jest bez żadnych kwalifikacji referencyjnych (pozwalających określić, czy chceszT
,T &
czyT &&
dla typu wywnioskowanegoT
).To
&&
jest nowa semantyka przenoszenia. Typ obsługujący semantykę przenoszenia implementuje konstruktora,T(T && other)
który optymalnie przenosi zawartość w nowym typie. Pozwala to obiektowi na zamianę reprezentacji wewnętrznej zamiast wykonywania głębokiej kopii.Dzięki temu możesz mieć coś takiego:
Więc:
wykona kopię zwracanego wektora (drogiego), ale:
zamieni wewnętrzną reprezentację wektora (wektor z
foo
i pusty wektor zvar
), więc będzie szybszy.Jest to używane w nowej składni pętli for:
W przypadku, gdy dla pętli trzyma
auto &&
wartości powrotnej zfoo
iitem
jest punktem odniesienia dla każdej wartościfoo
.źródło
auto&&
niczego nie ruszy, po prostu utworzy odniesienie. To, czy jest to odniesienie l-wartość, czy r-wartość, zależy od wyrażenia użytego do jego zainicjowania.std::vector
istd::string
są możliwe do zbudowania ruchu. Nie ma to nic wspólnego z typemvar
.