Sök…


Betydelse av värdekategori

Uttryck i C ++ tilldelas en viss värdekategori, baserat på resultatet av dessa uttryck. Värdekategorier för uttryck kan påverka upplösningen av C ++ -funktionens överbelastning.

Värdekategorier bestämmer två viktiga men separata egenskaper för ett uttryck. En egenskap är om uttrycket har identitet. Ett uttryck har identitet om det hänvisar till ett objekt som har ett variabelt namn. Variablen namn kanske inte är involverad i uttrycket, men objektet kan fortfarande ha ett.

Den andra egenskapen är om det är lagligt att implicit flytta från uttryckets värde. Eller mer specifikt, om uttrycket, när det används som en funktionsparameter, kommer att binda till r-värdet parametertyper eller inte.

C ++ definierar 3 värdekategorier som representerar den användbara kombinationen av dessa egenskaper: lvalue (uttryck med identitet men inte rörliga från), xvalue (uttryck med identitet som är rörliga från) och prvalue (uttryck utan identitet som är rörliga från). C ++ har inga uttryck som inte har någon identitet och inte kan flyttas från.

C ++ definierar två andra värdekategorier, vardera enbart baserade på en av dessa egenskaper: glvalue (uttryck med identitet) och rvalue (uttryck som kan flyttas från). Dessa fungerar som användbara grupperingar av de tidigare kategorierna.

Denna graf fungerar som en illustration:

Graf över värdekategorier på C ++ - språket

prvalue

Ett uttryck (ren-rvalue) är ett uttryck som saknar identitet, vars utvärdering vanligtvis används för att initialisera ett objekt och som implicit kan flyttas från. Dessa inkluderar, men är inte begränsade till:

  • Uttryck som representerar tillfälliga objekt, till exempel std::string("123") .
  • Ett funktionskalluttryck som inte returnerar en referens
  • En bokstavlig ( förutom en strängbokstavlig - de är lvaluer), sådan har 1 , true , 0.5f eller 'a'
  • Ett lambda-uttryck

Den inbyggda adressofoperatören ( & ) kan inte tillämpas på dessa uttryck.

xValue

Ett xvalue-uttryck (eXpiring-värde) är ett uttryck som har identitet och representerar ett objekt som implicit kan flyttas från. Den allmänna idén med xvalue-uttryck är att objektet de representerar kommer att förstöras snart (därmed "eXpiring" -delen), och därför implicit flyttning från dem är bra.

Given:

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

Lvärde

Ett lvalue-uttryck är ett uttryck som har identitet, men inte implicit kan flyttas från. Bland dessa är uttryck som består av ett variabelt namn, funktionsnamn, uttryck som är inbyggda dereferensoperatörsanvändningar och uttryck som hänvisar till lvaluereferenser.

Den typiska lvalue är helt enkelt ett namn, men lvalues kan också komma i andra smaker:

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

Medan de flesta bokstäver (t.ex. 4 , 'x' , etc.) är uppskattningar, är stränglitteraler dessutom lvaluer.

glvalue

Ett glvalue (ett "generaliserat lvalue") är ett uttryck som har identitet, oavsett om det kan flyttas från eller inte. Denna kategori inkluderar värderingar (uttryck som har identitet men inte kan flyttas från) och xvaluer (uttryck som har identitet och kan flyttas från), men utesluter värden (uttryck utan identitet).

Om ett uttryck har ett namn är det en 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

rvalue

Ett rvalueuttryck är varje uttryck som implicit kan flyttas från, oavsett om det har identitet.

Mer exakt kan rvalueuttryck användas som argument till en funktion som tar en parameter av typen T && (där T är typen av expr ). Endast rvalueuttryck får ges som argument till sådana funktionsparametrar; om ett uttryck som inte är rvalue används, kommer överbelastningsupplösning att välja vilken funktion som inte använder en rvalueferensparameter. Och om ingen finns, får du ett fel.

Kategorin av rvalueuttryck inkluderar alla xvalue- och prvalue-uttryck, och endast dessa uttryck.

Standardbiblioteksfunktionen std::move finns för att uttryckligen omvandla ett uttryck som inte är rvalue till en rvalue. Mer specifikt förvandlar det uttrycket till ett xvalue, eftersom även om det var ett identitetslöst prvalueuttryck tidigare, genom att skicka det som en parameter till std::move , får den identitet (funktionens parameternamn) och blir en xvalue.

Tänk på följande:

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 har en konstruktör som tar en enda parameter av typen std::string&& , vanligtvis kallad "flytta konstruktör". Men värdekategorin för uttrycket str är inte en rvalue (specifikt är det en lvalue), så det kan inte kalla den konstruktörens överbelastning. Istället kallar den const std::string& overload, kopikonstruktören.

Rad 3 ändrar saker. Returvärdet för std::move är en T&& , där T är bastypen för den överförda parametern. Så std::move(str) returnerar std::string&& . Ett funktionssamtal vem som är returvärde är en rvalue-referens är ett rvalue-uttryck (specifikt en xvalue), så det kan ringa flyttkonstruktorn för std::string . Efter rad 3 har str flyttats från (vem har nu innehållet är odefinierat).

Rad 4 överför en tillfällig till tilldelningsoperatören för std::string . Detta har en överbelastning som tar en std::string&& . Uttrycket std::string("new value") är ett rvaluexpression (specifikt en prvalue), så det kan kalla den överbelastningen. Således flyttas den temporära till str , ersätter det odefinierade innehållet med specifikt innehåll.

Rad 5 skapar en namngiven rvalue-referens som heter str_ref som refererar till str . Det är här som värdekategorier blir förvirrande.

Se, medan str_ref är en rvaluehenvisning till std::string , är värdekategorin för uttrycket str_ref inte en rvalue . Det är ett lvalue-uttryck. Ja verkligen. På grund av detta kan man inte kalla flyttkonstruktören för std::string med uttrycket str_ref . Rad 6 kopierar därför värdet på str till test3 .

För att flytta det, måste vi anställa std::move igen.



Modified text is an extract of the original Stack Overflow Documentation
Licensierat under CC BY-SA 3.0
Inte anslutet till Stack Overflow