W C ++ 03 wyrażenie to albo wartość, albo wartość .
W C ++ 11 wyrażenie może być:
- wartość r
- wartość
- xvalue
- glvalue
- prvalue
Dwie kategorie stały się pięcioma kategoriami.
- Jakie są te nowe kategorie wyrażeń?
- Jak te nowe kategorie odnoszą się do istniejących kategorii wartości i wartości?
- Czy kategorie wartości i wartości w C ++ 0x są takie same jak w C ++ 03?
- Dlaczego te nowe kategorie są potrzebne? Czy bogowie WG21 tylko próbują wprowadzić w błąd zwykłych śmiertelników?
c++
expression
c++-faq
c++11
James McNellis
źródło
źródło
string("hello") = string("world")
.Odpowiedzi:
Myślę, że ten dokument może służyć jako niezbyt krótkie wprowadzenie: n3055
Cała masakra rozpoczęła się od semantyki ruchu. Gdy mamy już wyrażenia, które można przenosić, a nie kopiować, nagle łatwe do uchwycenia reguły wymagały rozróżnienia między wyrażeniami, które można przenosić, i w jakim kierunku.
Z tego, co sądzę na podstawie szkicu, rozróżnienie wartości r / l pozostaje takie samo, tylko w kontekście przenoszenia rzeczy robi się bałagan.
Czy są potrzebne? Prawdopodobnie nie, jeśli chcemy utracić nowe funkcje. Ale aby umożliwić lepszą optymalizację, prawdopodobnie powinniśmy je przyjąć.
Cytując n3055 :
E
jest wyrażeniem typu wskaźnikowego, to*E
jest to wyrażenie wartości odnoszące się do obiektu lub funkcji, do którejE
wskazuje. Jako kolejny przykład wynikiem wywołania funkcji, której typem zwracanym jest odwołanie do wartości, jest wartość.]Dokument, o którym mowa, stanowi świetne odniesienie do tego pytania, ponieważ pokazuje dokładne zmiany standardu, które nastąpiły w wyniku wprowadzenia nowej nomenklatury.
źródło
FCD (n3092) ma doskonały opis:
Proponuję jednak przeczytać całą sekcję 3.10 Wartości i wartości lv .
Jeszcze raz:
Semantyka wartości r ewoluowała szczególnie wraz z wprowadzeniem semantyki ruchu.
Aby konstrukcja / przypisanie ruchów mogło zostać zdefiniowane i obsługiwane.
źródło
glvalue
tak jaklvalue
ilvalue
jakplvalue
, aby zachować spójność?Zacznę od twojego ostatniego pytania:
Standard C ++ zawiera wiele reguł dotyczących kategorii wartości wyrażenia. Niektóre reguły wprowadzają rozróżnienie między wartością lvalue a rvalue. Na przykład, jeśli chodzi o rozdzielczość przeciążenia. Inne zasady wprowadzają rozróżnienie między wartością glvalue a prvalue. Na przykład możesz mieć wartość glvalue z niekompletnym lub abstrakcyjnym typem, ale nie ma wartości z niekompletnym lub abstrakcyjnym typem. Zanim mieliśmy tę terminologię, reguły, które faktycznie muszą odróżniać glvalue / prvalue, odnosiły się do lvalue / rvalue i były albo niezamierzone, albo zawierały wiele wyjaśnień i wyjątków od reguły a la „... chyba że wartość jest spowodowana nienazwanym odniesienie do wartości ... ”. Wydaje się więc, że dobrym pomysłem jest nadanie pojęciom glvalues i prvalues własnej nazwy.
Nadal mamy warunki lvalue i rvalue, które są kompatybilne z C ++ 98. Właśnie podzieliliśmy wartości na dwie podgrupy, wartości i wartości, a wartości lvalu i xvale nazywamy wartościami glvalu. Xvalues to nowy rodzaj kategorii wartości dla nienazwanych odwołań do wartości. Każde wyrażenie jest jednym z tych trzech: lvalue, xvalue, prvalue. Diagram Venna wyglądałby tak:
Przykłady z funkcjami:
Ale nie zapominaj też, że nazwane referencje wartości są wartościami lv:
źródło
Nie sądzę, że inne odpowiedzi (dobre, choć wiele z nich jest) naprawdę zawierają odpowiedź na to konkretne pytanie. Tak, te kategorie i takie istnieją, aby umożliwić semantykę przenoszenia, ale złożoność istnieje z jednego powodu. Jest to jedna nienaruszalna zasada przenoszenia rzeczy w C ++ 11:
Poruszasz się tylko wtedy, gdy jest to bez wątpienia bezpieczne.
Właśnie dlatego te kategorie istnieją: aby móc mówić o wartościach, w których można bezpiecznie od nich przejść, i mówić o wartościach, w których nie ma.
W najwcześniejszej wersji referencji o wartości r ruch odbywał się łatwo. Zbyt łatwo. Łatwo było, że istniało wiele możliwości niejawnego przenoszenia rzeczy, gdy użytkownik tak naprawdę nie chciał.
Oto okoliczności, w których można bezpiecznie coś przenieść:
Jeśli to zrobisz:
Co to robi? W starszych wersjach specyfikacji, zanim pojawiło się 5 wartości, sprowokowałoby to ruch. Oczywiście, że tak. Przekazałeś odwołanie do wartości do konstruktora, a zatem wiąże się ono z konstruktorem, który pobiera odwołanie do wartości. To oczywiste.
Jest z tym tylko jeden problem; nie prosiłeś go o przeniesienie. Och, możesz powiedzieć, że
&&
powinna to być wskazówka, ale to nie zmienia faktu, że złamała zasadę.val
nie jest tymczasowy, ponieważ tymczasowe nie mają nazw. Być może przedłużyłeś czas życia tymczasowego, ale to oznacza, że nie jest tymczasowy ; jest jak każda inna zmienna stosu.Jeśli nie jest to tymczasowe i nie poprosiłeś o jego przeniesienie, wówczas poruszanie się jest złe.
Oczywistym rozwiązaniem jest zrobienie
val
wartości. Oznacza to, że nie możesz się z niego ruszać. Ok dobrze; nazywa się, więc jest to wartość.Gdy to zrobisz, nie będziesz już mógł powiedzieć, że
SomeType&&
oznacza to to samo wszędzie. Wprowadzono teraz rozróżnienie między nazwanymi referencjami wartości i nienazwanymi referencjami wartości. Nazwane odniesienia wartości są wartościami lv; to było nasze rozwiązanie powyżej. Co więc nazywamy referencjami wartości bez nazwy (wartość zwracana zFunc
góry)?To nie jest wartość, ponieważ nie można przejść od wartości. A my potrzebujemy , aby móc przejść przez przekazujących
&&
; jak inaczej mógłbyś wyraźnie powiedzieć, żeby coś przenieść? Wstd::move
końcu to właśnie powraca. To nie jest wartość (w starym stylu), ponieważ może być po lewej stronie równania (rzeczy są w rzeczywistości nieco bardziej skomplikowane, zobacz to pytanie i komentarze poniżej). To nie jest ani wartość, ani wartość; to nowy rodzaj rzeczy.To, co mamy, to wartość, którą można traktować jako wartość, z tą różnicą, że można ją w sposób niejawny przenosić. Nazywamy to xwartością.
Zauważ, że wartości x są tym, co sprawia, że zdobywamy pozostałe dwie kategorie wartości:
Wartość jest tak naprawdę nową nazwą dla poprzedniego typu wartości, tzn. Są to wartości, które nie są wartościami.
Glvalues to połączenie wartości xvalues i lvalues w jednej grupie, ponieważ mają one wiele wspólnych właściwości.
Tak naprawdę wszystko sprowadza się do wartości i potrzeby ograniczenia ruchu do dokładnie i tylko niektórych miejsc. Miejsca te są zdefiniowane według kategorii wartości; wartości są niejawnymi ruchami, a wartości są jawnymi ruchami (
std::move
zwraca wartość x).źródło
&&
.X foo(); foo() = X;
... Z tego podstawowego powodu nie mogę do końca podążać za powyższą doskonałą odpowiedzią, ponieważ tak naprawdę rozróżniasz tylko nowa xvalue i prvalue w starym stylu, oparte na fakcie, że może być na lhs.X
bycie klasą;X foo();
bycie deklaracją funkcji ifoo() = X();
bycie wierszem kodu. (foo() = X();
W moim powyższym komentarzu pominąłem drugi zestaw nawiasów .) Pytanie, które właśnie wysłałem z zaznaczonym użyciem, znajduje się na stackoverflow.com/questions/15482508/...IMHO, najlepsze wyjaśnienie jego znaczenia dało nam Stroustrup + wziął pod uwagę przykłady Dániela Sándora i Mohana :
Stroustrup:
źródło
lvalue
s, wszystkie inne literały toprvalue
s. Ściśle mówiąc, można argumentować, że literały nie łańcuchowe powinny być nieruchome, ale nie w taki sposób zapisuje się standard.WPROWADZENIE
ISOC ++ 11 (oficjalnie ISO / IEC 14882: 2011) to najnowsza wersja standardu języka programowania C ++. Zawiera kilka nowych funkcji i pojęć, na przykład:
Jeśli chcielibyśmy zrozumieć koncepcje nowych kategorii wartości wyrażeń, musimy zdawać sobie sprawę z tego, że istnieją odwołania do wartości i wartości. Lepiej jest wiedzieć, że wartości można przekazywać do odwołań do wartości nie stałych.
Zyskamy trochę intuicji pojęć kategorii wartości, cytując podrozdział zatytułowany „Wartości lvalu i wartości rvalu” z roboczego szkicu N3337 (najbardziej podobny szkic do opublikowanego standardu ISOC ++ 11).
Nie jestem jednak pewien, czy ten podrozdział wystarcza do jasnego zrozumienia pojęć, ponieważ „zwykle” nie jest tak naprawdę ogólny, „pod koniec swojego życia” nie jest tak naprawdę konkretny, „uwzględnianie odniesień do wartości” nie jest do końca jasne, i „Przykład: Wynikiem wywołania funkcji, której typem zwracanym jest odwołanie do wartości, jest wartość x.” brzmi jak wąż gryzie ogonem.
KATEGORIE WARTOŚCI PODSTAWOWEJ
Każde wyrażenie należy do dokładnie jednej podstawowej kategorii wartości. Te kategorie wartości to kategorie lvalue, xvalue i prvalue.
wartości
Wyrażenie E należy do kategorii wartości tylko i tylko wtedy, gdy E odnosi się do jednostki, która JUŻ Miała tożsamość (adres, nazwę lub alias), która umożliwia jej dostęp poza E.
wartości
Wyrażenie E należy do kategorii xvalue tylko wtedy, gdy jest
- wynik wywołania funkcji, niejawnie lub jawnie, której typem zwracanym jest odwołanie do wartości typu zwracanego obiektu, lub
- rzutowanie na odwołanie wartości do typu obiektu, lub
- wyrażenie dostępu do elementu klasy oznaczające element danych niestatycznych typu innego niż referencyjny, w którym wyrażenie obiektowe jest wartością x, lub
- wyrażenie wskaźnik do elementu, w którym pierwszy operand jest wartością x, a drugi operand jest wskaźnikiem do elementu danych.
Zauważ, że efekt powyższych reguł jest taki, że nazwane odwołania wartości do obiektów są traktowane jako wartości lv, a nienazwane odniesienia wartości do obiektów są traktowane jak wartości x; referencje wartości do funkcji są traktowane jako wartości lv, niezależnie od tego, czy są nazwane czy nie.
wartości
Wyrażenie E należy do kategorii prvalue wtedy i tylko wtedy, gdy E nie należy ani do wartości lvalue, ani do kategorii xvalue.
KATEGORIE MIESZANEJ WARTOŚCI
Istnieją dwie dalsze ważne kategorie mieszanych wartości. Te kategorie wartości to kategorie wartości i wartości glvalue.
wartości
Wyrażenie E należy do kategorii wartości, tylko jeśli E należy do kategorii wartości lub do kategorii wartości.
Zauważ, że ta definicja oznacza, że wyrażenie E należy do kategorii wartości, tylko wtedy, gdy E odnosi się do encji, która nie miała żadnej tożsamości, która czyni ją dostępną poza E YET.
wartości glvalues
Wyrażenie E należy do kategorii glvalue wtedy i tylko wtedy, gdy E należy do kategorii lvalue lub do kategorii xvalue.
PRAKTYCZNA ZASADA
Scott Meyer opublikował bardzo przydatną ogólną zasadę odróżniającą wartości od wartości lv.
źródło
struct As{void f(){this;}}
żethis
zmienna jest prvalue. Myślałem, żethis
powinna być wartość. Dopóki standard 9.3.2 nie mówi: W treści niestatystycznej (9,3) funkcji składowej słowo kluczowe jest wyrażeniem prvalue.this
jest prvalue ale*this
jest lwartością"www"
nie zawsze ma ten sam adres. Jest to wartość, ponieważ jest tablicą .Kategorie C ++ 03 są zbyt ograniczone, aby poprawnie wprowadzić wprowadzenie referencji wartości do atrybutów wyrażeń.
Wraz z ich wprowadzeniem powiedziano, że nienazwane odwołanie do wartości ocenia się na wartość taką, że rozdzielczość przeciążenia wolałaby powiązania odniesienia do wartości, co spowodowałoby, że wybrałby konstruktor ruchu zamiast konstruktora kopiowania. Okazało się jednak, że powoduje to problemy, na przykład w przypadku typów dynamicznych i kwalifikacji.
Aby to pokazać, zastanów się
W wersjach roboczych przed wartością x było to dozwolone, ponieważ w C ++ 03 wartości różnych typów nie są nigdy kwalifikowane do cv. Ale to ma oznaczać, że
const
ma zastosowanie w przypadku RValue-odniesienia, bo tu nie odnosi się do obiektów (= pamięć!), I upuszczanie const z rvalues non-klasa jest głównie z tego powodu, że nie ma żadnego obiektu dookoła.Problem z typami dynamicznymi ma podobny charakter. W C ++ 03 wartości rv typu klasy mają znany typ dynamiczny - jest to typ statyczny tego wyrażenia. Ponieważ, aby mieć inny sposób, potrzebujesz referencji lub dereferencji, które oceniają na wartość. Nie jest to prawdą w przypadku nienazwanych odwołań do wartości, ale mogą one wykazywać zachowanie polimorficzne. Aby to rozwiązać,
nienazwane odwołania do wartości stają się wartościami x . Mogą być zakwalifikowani i potencjalnie mogą mieć inny typ dynamiczny. Robią, tak jak zamierzają, wolą odwołania do wartości podczas przeciążania i nie będą wiązać się z nie-stałymi odwołaniami do wartości.
To, co poprzednio było wartością (literały, obiekty tworzone przez rzutowanie na typy niebędące odniesieniami), teraz staje się wartością prywatną . Mają takie same preferencje jak wartości x podczas przeciążania.
To, co poprzednio było wartością, pozostaje wartością.
Wykonano dwie grupy, aby uchwycić te, które można zakwalifikować i mogą mieć różne typy dynamiczne ( glvalues ) oraz te, w których przeciążenie woli wiązanie odniesienia wartości (wartość rvalues ).
źródło
Walczyłem z tym przez długi czas, dopóki nie natknąłem się na wyjaśnienie kategorii wartości cppreference.com .
W rzeczywistości jest to dość proste, ale uważam, że często wyjaśnia się je w sposób trudny do zapamiętania. Tutaj wyjaśniono to bardzo schematycznie. Zacytuję niektóre części strony:
źródło
Wartość C ++ 03 jest nadal wartością C ++ 11, podczas gdy wartość C ++ 03 w C ++ 11 nazywa się wartością.
źródło
Jeden dodatek do doskonałych odpowiedzi powyżej, w kwestii, która mnie zdezorientowała, nawet po przeczytaniu Stroustrupa i przekonaniu, że rozumiem rozróżnienie wartość / wartość. Kiedy widzisz
int&& a = 3
,to bardzo kuszące, aby przeczytać ten
int&&
typ i stwierdzić, żea
jest to wartość. To nie jest:a
ma nazwę i jest ipso facto wartością. Nie myśl o tym&&
jako o części tego typua
; to po prostu coś, co mówi ci, z czyma
wolno się wiązać.Ma to szczególne znaczenie dla
T&&
argumentów typu w konstruktorach. Jeśli napiszeszFoo::Foo(T&& _t) : t{_t} {}
skopiujesz
_t
dot
. PotrzebujeszFoo::Foo(T&& _t) : t{std::move(_t)} {}
jeśli chcesz się przenieść. Czy mój kompilator ostrzegłby mnie przed pominięciemmove
!źródło
a
można się wiązać”: Jasne, ale w liniach 2 i 3 twoje zmienne to c & b, i to nie jest to, co wiąże się z tym, a typa
jest tutaj nieistotny, prawda? Linie byłyby takie same, gdybya
zostały zadeklarowaneint a
. Główna różnica polega tutaj na tym, że w wierszu 1 nie trzebaconst
przypisywać do 3.Ponieważ poprzednie odpowiedzi wyczerpująco obejmowały teorię dotyczącą kategorii wartości, jest jeszcze jedna rzecz, którą chciałbym dodać: możesz się nią bawić i przetestować.
W przypadku niektórych praktycznych eksperymentów z kategoriami wartości można skorzystać ze specyfikatora decltype . Jego zachowanie wyraźnie rozróżnia trzy podstawowe kategorie wartości (xvalue, lvalue i prvalue).
Korzystanie z preprocesora pozwala zaoszczędzić trochę pisania ...
Główne kategorie:
Mieszane kategorie:
Teraz możemy odtworzyć (prawie) wszystkie przykłady z preferencji cp na kategorię wartości .
Oto kilka przykładów z C ++ 17 (dla terse static_assert):
Kategorie mieszane są trochę nudne, kiedy już wymyślisz kategorię podstawową.
Aby uzyskać więcej przykładów (i eksperymentów), sprawdź poniższy link w eksploratorze kompilatorów . Jednak nie zawracaj sobie głowy czytaniem zestawu. Dodałem wiele kompilatorów, aby upewnić się, że działa we wszystkich popularnych kompilatorach.
źródło
#define IS_GLVALUE(X) IS_LVALUE(X) || IS_XVALUE(X)
powinieneś#define IS_GLVALUE(X) (IS_LVALUE(X) || IS_XVALUE(X))
spojrzeć na to, co się stanie, jeśli wy&&
dwojeIS_GLVALUE
.