Przyglądałem się niektórym nowym funkcjom C ++ 11 i zauważyłem, że podwójny znak & w deklaracji zmiennych, takich jak T&& var
.
Na początek, jak nazywa się ta bestia? Chciałbym, aby Google umożliwił nam wyszukiwanie takich znaków interpunkcyjnych.
Co to dokładnie znaczy?
Na pierwszy rzut oka wydaje się, że jest to podwójne odniesienie (podobnie jak podwójne wskaźniki w stylu C T** var
), ale trudno mi się zastanowić nad przypadkiem użycia w tym celu.
c++
c++11
rvalue-reference
c++-faq
perfect-forwarding
paxdiablo
źródło
źródło
:)
Odpowiedzi:
Deklaruje odniesienie do wartości (dokument propozycji norm).
Oto wstępem do rvalue odniesień .
Oto fantastyczny wygląd pogłębione w rvalue odnośnikach jednej standardowej biblioteki Microsoft programistów .
Największą różnicą między referencją C ++ 03 (obecnie nazywaną referencją lvalue w C ++ 11) jest to, że może ona wiązać się z wartością jak tymczasowa bez konieczności bycia const. Zatem ta składnia jest teraz legalna:
odniesienia do wartości uwzględniają przede wszystkim:
Przenieś semantykę . Można teraz zdefiniować konstruktor ruchu i operator przypisania ruchu, który pobiera odwołanie do wartości zamiast zwykłego odwołania do wartości stałej. Ruch działa jak kopia, ale nie jest zobowiązany do zachowania źródła bez zmian; w rzeczywistości zwykle modyfikuje źródło tak, że nie jest już właścicielem przeniesionych zasobów. Jest to idealne rozwiązanie do eliminowania obcych kopii, szczególnie w standardowych implementacjach bibliotek.
Na przykład konstruktor kopii może wyglądać następująco:
Jeśli ten konstruktor przejdzie tymczasowo, kopia byłaby niepotrzebna, ponieważ wiemy, że tymczasowy zostanie po prostu zniszczony; dlaczego nie skorzystać z zasobów tymczasowo już przydzielonych? W C ++ 03 nie ma sposobu, aby zapobiec kopiowaniu, ponieważ nie jesteśmy w stanie stwierdzić, że otrzymaliśmy tymczasowe zezwolenie. W C ++ 11 możemy przeciążać konstruktor ruchu:
Zauważ tutaj dużą różnicę: konstruktor ruchu faktycznie modyfikuje swój argument. To skutecznie „przeniósłby” tymczasowość do budowanego obiektu, eliminując w ten sposób niepotrzebną kopię.
Konstruktor ruchu byłby używany do tymczasowych i nie stałych const referencji wartości, które są jawnie konwertowane na referencje wartości za pomocą
std::move
funkcji (po prostu wykonuje konwersję). Poniższy kod wywołuje konstruktor przenoszenia dlaf1
if2
:Idealne przekazywanie . Odwołania do wartości pozwalają nam poprawnie przekazywać argumenty dla funkcji szablonowych. Weźmy na przykład tę funkcję fabryczną:
Jeśli zadzwonimy
factory<foo>(5)
, wywnioskowany zostanie argumentint&
, który nie będzie wiązał się z literałem 5, nawet jeślifoo
konstruktor przyjmuje argumentint
. Cóż, moglibyśmy zamiast tego użyćA1 const&
, ale co jeślifoo
argument konstruktora zostanie przyjęty przez odwołanie inne niż const? Aby prawdziwie rodzajowe funkcji fabryki, mielibyśmy do przeciążenia fabrykę naA1&
i naA1 const&
. Może to być w porządku, jeśli fabryka przyjmuje 1 typ parametru, ale każdy dodatkowy typ parametru pomnożyłby niezbędne ustawienie przeciążenia przez 2. To bardzo szybko nie do utrzymania.odwołania do wartości rozwiązują ten problem, umożliwiając standardowej bibliotece zdefiniowanie
std::forward
funkcji, która może poprawnie przekazywać odniesienia do wartości / wartości. Aby uzyskać więcej informacji o tymstd::forward
, jak działa, zobacz tę doskonałą odpowiedź .To pozwala nam zdefiniować funkcję fabryczną w następujący sposób:
Teraz argument rvalue / lvalue-ness argumentu zostaje zachowany po przekazaniu do
T
konstruktora. Oznacza to, że jeśli wywoływana jest fabryka z wartością,T
konstruktor jest wywoływany z wartością. Jeśli fabryka jest wywoływana z wartością,T
konstruktor jest wywoływany z wartością. Ulepszona funkcja fabryczna działa dzięki jednej specjalnej zasadzie:W ten sposób możemy użyć fabryki tak:
Ważne właściwości odniesienia wartości :
float f = 0f; int&& i = f;
jest dobrze uformowany, ponieważ liczba zmiennoprzecinkowa jest domyślnie przekształcalna na int; odniesienie dotyczyłoby tymczasowego wyniku konwersji.std::move
połączenie jest konieczne w:foo&& r = foo(); foo f = std::move(r);
źródło
Named rvalue references are lvalues. Unnamed rvalue references are rvalues.
; nie wiedząc o tym, starałem się zrozumieć, dlaczego ludzieT &&t; std::move(t);
od dawna robią sobie lekarzy przeprowadzek i tym podobne.int x; int &&rrx = x;
Nie kompilują się już w GCC)typename identity<T>::type& a
równoważne zT&
?Oznacza odniesienie do wartości. Odwołania do wartości będą się wiązały tylko z obiektami tymczasowymi, chyba że zostanie wyraźnie wygenerowane inaczej. Służą do zwiększania wydajności obiektów w określonych okolicznościach oraz do zapewnienia funkcji znanej jako doskonałe przekazywanie, co znacznie upraszcza kod szablonu.
W C ++ 03 nie można odróżnić kopii niemodyfikowalnej wartości od wartości.
W C ++ 0x tak nie jest.
Zastanów się nad implementacją tych konstruktorów. W pierwszym przypadku ciąg musi wykonać kopię, aby zachować semantykę wartości, co obejmuje nowy przydział sterty. Jednak w drugim przypadku wiemy z góry, że obiekt, który został przekazany naszemu konstruktorowi, jest natychmiast spowodowany zniszczeniem i nie musi pozostać nietknięty. W tym scenariuszu, który jest znacznie bardziej wydajny, możemy skutecznie zamienić wewnętrzne wskaźniki i nie wykonywać żadnego kopiowania. Przenieś semantykę na korzyść każdej klasy, która ma kosztowne lub zabronione kopiowanie wewnętrznych zasobów. Rozważmy przypadek
std::unique_ptr
- teraz, gdy nasza klasa potrafi rozróżniać między tymczasowymi i nie-tymczasowymi, możemy sprawić, aby semantyka przenoszenia działała poprawnie, tak abyunique_ptr
nie można było skopiować, ale można ją przenieść, co oznacza, żestd::unique_ptr
mogą być legalnie przechowywane w standardowych kontenerach, sortowane itp., podczas gdy C ++ 03std::auto_ptr
nie.Teraz rozważamy inne zastosowanie referencji wartości - idealne przekazywanie. Rozważ kwestię związania odniesienia do odniesienia.
Nie mogę sobie przypomnieć, co na ten temat mówi C ++ 03, ale w C ++ 0x typ wynikowy podczas obsługi odwołań do wartości jest krytyczny. Odwołanie do wartości typu T, gdzie T jest typem odniesienia, staje się odwołaniem typu T.
Rozważ najprostszą funkcję szablonu - min. I maks. W C ++ 03 musisz ręcznie przeciążać wszystkie cztery kombinacje const i non-const. W C ++ 0x to tylko jedno przeciążenie. W połączeniu z różnymi szablonami umożliwia to idealne przekazywanie.
Zrezygnowałem z odliczenia typu zwrotu, ponieważ nie mogę sobie przypomnieć, jak to się robi odręcznie, ale ta min może zaakceptować dowolną kombinację wartości lvalu, wartości rv, wartości stałych lv.
źródło
std::forward<A>(aref) < std::forward<B>(bref)
? i nie sądzę, aby ta definicja była poprawna, gdy spróbujesz naprzódint&
ifloat&
. Lepiej upuść szablon formularza jednego typu.Termin
T&&
używany w przypadku dedukcji typu (na przykład do idealnego przekazywania) jest znany potocznie jako odniesienie do przekazywania . Termin „uniwersalne odniesienie” został wymyślony przez Scotta Meyersa w tym artykule , ale później został zmieniony.Jest tak, ponieważ może to być wartość r lub wartość l.
Przykładami są:
Więcej dyskusji można znaleźć w odpowiedzi na: Składnia uniwersalnych odniesień
źródło
Odwołanie do wartości jest typem, który zachowuje się podobnie jak zwykłe odwołanie X i, z kilkoma wyjątkami. Najważniejsze jest to, że jeśli chodzi o rozdzielczość przeciążenia funkcji, wartości lv preferują stare wartości referencyjne wartości, podczas gdy wartości wolą nowe referencje wartości:
Czym jest wartość? Wszystko, co nie jest wartością. Wartość jest wyrażeniem odnoszącym się do lokalizacji w pamięci i pozwala nam pobrać adres tej lokalizacji za pośrednictwem operatora &.
Prawie łatwiej jest najpierw zrozumieć, co wartości osiągają na przykładzie:
Konstruktor i operatory przypisania zostały przeciążone wersjami, które pobierają odwołania do wartości. Odwołania do wartości pozwalają na rozgałęzienie funkcji w czasie kompilacji (przez rozwiązanie przeciążenia) pod warunkiem „Czy jestem wywoływany na wartości lub wartości?”. To pozwoliło nam stworzyć bardziej wydajne konstruktory i operatory przypisania powyżej, które przenoszą zasoby raczej je kopiują.
Kompilator automatycznie rozgałęzia się w czasie kompilacji (w zależności od tego, czy jest wywoływany dla wartości czy wartości), wybierając, czy należy wywołać konstruktora ruchu, czy operator przypisania ruchu.
Podsumowując: odniesienia do wartości pozwalają na semantykę przenoszenia (i doskonałe przekazywanie, omówione w linku do artykułu poniżej).
Jednym praktycznym, łatwym do zrozumienia przykładem jest szablon klasy std :: unique_ptr . Ponieważ Unique_ptr utrzymuje wyłączną własność swojego podstawowego wskaźnika surowego, unikalne pliki_ptr nie mogą być kopiowane. Naruszyłoby to ich niezmienność wyłącznej własności. Nie mają więc konstruktorów kopiowania. Ale mają konstruktory ruchów:
static_cast<unique_ptr<int[]>&&>(ptr)
zwykle odbywa się za pomocą std :: moveDoskonałym artykułem wyjaśniającym to wszystko i wiele więcej (na przykład, w jaki sposób wartości pozwalają na idealne przekazywanie i co to oznacza) z wieloma dobrymi przykładami jest wyjaśnienie referencji wartości C ++ Thomasa Beckera . Ten post opierał się w dużej mierze na jego artykule.
Krótsze wprowadzenie to Krótkie wprowadzenie do odniesień do wartości przez Stroutrup, i in. glin
źródło
Sample(const Sample& s)
musi również skopiować zawartość? To samo pytanie dla „operatora przypisania kopii”.