C++
Kategorie wartości
Szukaj…
Znaczenie kategorii wartości
Wyrażeniom w C ++ przypisywana jest określona kategoria wartości, na podstawie wyniku tych wyrażeń. Kategorie wartości wyrażeń mogą wpływać na rozdzielczość przeciążenia funkcji C ++.
Kategorie wartości określają dwie ważne, ale odrębne właściwości dotyczące wyrażenia. Jedną właściwością jest to, czy wyrażenie ma tożsamość. Wyrażenie ma tożsamość, jeśli odnosi się do obiektu o nazwie zmiennej. Nazwa zmiennej może nie być zaangażowana w wyrażenie, ale obiekt może nadal ją mieć.
Inną właściwością jest to, czy niejawne jest legalne odejście od wartości wyrażenia. A dokładniej, czy wyrażenie użyte jako parametr funkcji będzie wiązało się z typami parametrów o wartości r, czy nie.
C ++ definiuje 3 kategorie wartości, które reprezentują użyteczną kombinację tych właściwości: lvalue (wyrażenia o tożsamości, ale nie można ich przenosić), xvalue (wyrażenia o tożsamości, które można przenieść) i prvalue (wyrażenia bez tożsamości, które można przenieść). C ++ nie ma wyrażeń, które nie mają tożsamości i nie można ich przenieść.
C ++ definiuje dwie inne kategorie wartości, z których każda opiera się wyłącznie na jednej z tych właściwości: glvalue (wyrażenia o tożsamości) i rvalue (wyrażenia, z których można przenieść). Działają one jako przydatne grupy poprzednich kategorii.
Ten wykres służy jako ilustracja:
prvalue
Wyrażenie prvalue (pure-rvalue) jest wyrażeniem pozbawionym tożsamości, którego ocena jest zwykle używana do inicjalizacji obiektu i od którego można niejawnie przenieść. Należą do nich między innymi:
- Wyrażenia reprezentujące obiekty tymczasowe, takie jak
std::string("123")
. - Wyrażenie wywołania funkcji, które nie zwraca odwołania
- Dosłowny ( z wyjątkiem literału łańcuchowego - są to wartości lv), taki ma
1
,true
,0.5f
lub'a'
- Wyrażenie lambda
W przypadku tych wyrażeń nie można zastosować wbudowanego adresu operatora ( &
).
xvalue
Wyrażenie xvalue (wartość eXpiring) jest wyrażeniem, które ma tożsamość i reprezentuje obiekt, z którego można niejawnie przenieść się. Ogólną ideą wyrażeń xvalue jest to, że reprezentowany przez nich obiekt zostanie wkrótce zniszczony (stąd część „eXpiring”), a zatem niejawne odejście od nich jest w porządku.
Dany:
struct X { int n; };
extern X x;
4; // prvalue: does not have an identity
x; // lvalue
x.n; // lvalue
std::move(x); // xvalue
std::forward<X&>(x); // lvalue
X{4}; // prvalue: does not have an identity
X{4}.n; // xvalue: does have an identity and denotes resources
// that can be reused
wartość
Wyrażenie lvalue jest wyrażeniem, które ma tożsamość, ale nie może być niejawnie przeniesione z niego. Wśród nich są wyrażenia składające się z nazwy zmiennej, nazwy funkcji, wyrażeń, które są wbudowane w operatory dereferencji oraz wyrażeń odwołujących się do odwołań do wartości.
Typowa wartość to po prostu nazwa, ale wartości mogą również występować w innych smakach:
struct X { ... };
X x; // x is an lvalue
X* px = &x; // px is an lvalue
*px = X{}; // *px is also an lvalue, X{} is a prvalue
X* foo_ptr(); // foo_ptr() is a prvalue
X& foo_ref(); // foo_ref() is an lvalue
Dodatkowo, podczas gdy większość literałów (np. 4
, 'x'
itp.) To wartości, wartości literalne są wartościami lvalu.
glvalue
Wyrażenie glvalue („uogólniona lvalue”) to każde wyrażenie, które ma tożsamość, niezależnie od tego, czy można je przenieść, czy nie. Ta kategoria obejmuje wartości lvalu (wyrażenia, które mają tożsamość, ale których nie można przenieść) i wartości xv (wyrażenia, które mają tożsamość i można je przenosić), ale nie obejmuje wartości prvalues (wyrażenia bez tożsamości).
Jeśli wyrażenie ma nazwę , jest to glvalue:
struct X { int n; };
X foo();
X x;
x; // has a name, so it's a glvalue
std::move(x); // has a name (we're moving from "x"), so it's a glvalue
// can be moved from, so it's an xvalue not an lvalue
foo(); // has no name, so is a prvalue, not a glvalue
X{}; // temporary has no name, so is a prvalue, not a glvalue
X{}.n; // HAS a name, so is a glvalue. can be moved from, so it's an xvalue
wartość r
Wyrażenie wartości jest dowolnym wyrażeniem, z którego można pośrednio przenieść, niezależnie od tego, czy ma tożsamość.
Dokładniej, wyrażenia rvalue mogą być użyte jako argument funkcji, która przyjmuje parametr typu T &&
(gdzie T
jest typem expr
). Jako argumenty takich parametrów funkcji można podać tylko wyrażenia wartości; jeśli zostanie użyte wyrażenie nie będące wartościami, wówczas rozwiązanie przeciążenia wybierze dowolną funkcję, która nie używa parametru odniesienia wartości. A jeśli nie istnieje, pojawia się błąd.
Kategoria wyrażeń rvalue obejmuje wszystkie wyrażenia xvalue i prvalue i tylko te wyrażenia.
Standardowa funkcja biblioteczna std::move
istnieje w celu jawnego przekształcenia wyrażenia nieuwartościowego w wartość. Mówiąc dokładniej, zamienia wyrażenie w wartość x, ponieważ nawet jeśli wcześniej było to wyrażenie wartości bez tożsamości, przekazując je jako parametr do std::move
, to zyskuje tożsamość (nazwa parametru funkcji) i staje się wartością x.
Rozważ następujące:
std::string str("init"); //1
std::string test1(str); //2
std::string test2(std::move(str)); //3
str = std::string("new value"); //4
std::string &&str_ref = std::move(str); //5
std::string test3(str_ref); //6
std::string
ma konstruktor, który przyjmuje pojedynczy parametr typu std::string&&
, zwany potocznie „konstruktorem ruchu”. Jednak kategoria wartości wyrażenia str
nie jest wartością (w szczególności jest to wartość), więc nie może wywołać przeciążenia tego konstruktora. Zamiast tego wywołuje const std::string&
overload, konstruktor kopiujący.
Linia 3 zmienia rzeczy. Zwracana wartość std::move
to T&&
, gdzie T
jest podstawowym rodzajem przekazywanego parametru. Zatem std::move(str)
zwraca std::string&&
. Wywołanie funkcji, którego wartością zwracaną jest odwołanie do wartości, jest wyrażeniem wartości (w szczególności wartością x), więc może wywołać konstruktor ruchu std::string
. Po linii 3 str
został przeniesiony (którego treść jest teraz niezdefiniowana).
Linia 4 przekazuje tymczasowo operatorowi przypisania std::string
. Ma to przeciążenie, które wymaga std::string&&
. Wyrażenie std::string("new value")
jest wyrażeniem rvalue (konkretnie prvalue), więc może wywołać to przeciążenie. Tym samym plik tymczasowy jest przenoszony do str
, zastępując niezdefiniowaną zawartość określoną treścią.
Wiersz 5 tworzy nazwane odwołanie do wartości o nazwie str_ref
które odnosi się do str
. To tutaj kategorie wartości stają się mylące.
Widzisz, podczas gdy str_ref
jest odwołaniem do wartości std::string
, kategoria wartości wyrażenia str_ref
nie jest wartością . Jest to wyrażenie wartości. Tak naprawdę. Z tego powodu nie można wywołać konstruktora ruchu std::string
z wyrażeniem str_ref
. Wiersz 6 kopiuje zatem wartość str
do test3
.
Aby go przenieść, musielibyśmy ponownie zastosować std::move
.