C++
Waardecategorieën
Zoeken…
Betekenis van waardecategorieën
Aan expressies in C ++ wordt een bepaalde waardecategorie toegewezen op basis van het resultaat van die expressies. Waardecategorieën voor uitdrukkingen kunnen de overbelastingsresolutie van de C ++ functie beïnvloeden.
Waardecategorieën bepalen twee belangrijke, maar afzonderlijke eigenschappen van een uitdrukking. Eén eigenschap is of de uitdrukking identiteit heeft. Een uitdrukking heeft een identiteit als deze verwijst naar een object met een variabelenaam. De variabelenaam is mogelijk niet betrokken bij de uitdrukking, maar het object kan er toch een hebben.
De andere eigenschap is of het legaal is om impliciet van de waarde van de uitdrukking af te wijken. Of meer specifiek, of de uitdrukking, wanneer gebruikt als een functieparameter, al dan niet zal binden aan parameterwaarden voor r-waarden.
C ++ definieert 3 waardecategorieën die de nuttige combinatie van deze eigenschappen vertegenwoordigen: lvalue (expressies met identiteit maar niet verplaatsbaar van), xvalue (expressies met identiteit die verplaatsbaar zijn van) en prvalue (expressies zonder identiteit die verplaatsbaar zijn van). C ++ heeft geen uitdrukkingen die geen identiteit hebben en niet kunnen worden verplaatst.
C ++ definieert twee andere waardecategorieën, elk uitsluitend gebaseerd op een van deze eigenschappen: glvalue (expressies met identiteit) en rvalue (expressies waaruit kan worden verplaatst). Deze fungeren als nuttige groeperingen van de voorgaande categorieën.
Deze grafiek dient ter illustratie:
prvalue
Een prvalue (pure-rvalue) expressie is een expressie zonder identiteit, waarvan de evaluatie meestal wordt gebruikt om een object te initialiseren en waarvan impliciet kan worden verplaatst. Deze omvatten, maar zijn niet beperkt tot:
- Uitdrukkingen die tijdelijke objecten vertegenwoordigen, zoals
std::string("123")
. - Een functie-aanroepuitdrukking die geen verwijzing retourneert
- Een letterlijke ( behalve een letterlijke tekenreeks - dat zijn lvalues), zoals
1
,true
,0.5f
of'a'
- Een lambda-uitdrukking
Het ingebouwde adres van operator ( &
) kan niet worden toegepast op deze expressies.
xWaarde
Een xvalue-expressie (eXpiring-waarde) is een expressie die een identiteit heeft en een object vertegenwoordigt waarvan impliciet kan worden verplaatst. Het algemene idee met xvalue-uitdrukkingen is dat het object dat ze vertegenwoordigen binnenkort zal worden vernietigd (vandaar het "eXpiring" -gedeelte), en daarom impliciet van hen weggaan prima is.
Gegeven:
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
lvalue
Een lvalue-expressie is een expressie met een identiteit, maar waarvan niet impliciet kan worden verplaatst. Hieronder bevinden zich uitdrukkingen die bestaan uit een variabelenaam, functienaam, uitdrukkingen die zijn ingebouwd in de dereference-operator en uitdrukkingen die verwijzen naar waardeverwijzingen.
De typische waarde is gewoon een naam, maar waarden kunnen ook in andere smaken voorkomen:
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
Hoewel de meeste letterlijke waarden (bijvoorbeeld 4
, 'x'
, enz.) Prvalues zijn, zijn stringliterals bovendien lvalues.
glvalue
Een glvalue-uitdrukking (een "gegeneraliseerde waarde") is elke expressie met een identiteit, ongeacht of deze kan worden verplaatst of niet. Deze categorie bevat lvalues (expressies die identiteit hebben maar niet kunnen worden verplaatst) en xvalues (expressies die identiteit hebben en waaruit kan worden verplaatst), maar sluit prvalues (expressies zonder identiteit) uit.
Als een uitdrukking een naam heeft , is het een 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
Een rvalue-expressie is elke expressie waarvan impliciet kan worden verplaatst, ongeacht of deze een identiteit heeft.
Meer in het bijzonder kunnen rvalue-uitdrukkingen worden gebruikt als argument voor een functie waarvoor een parameter van het type T &&
(waarbij T
het type expr
). Alleen rvalue-expressies kunnen als argumenten voor dergelijke functieparameters worden gegeven; als een niet-rvalue-expressie wordt gebruikt, kiest de overbelastingsresolutie elke functie die geen rvalue-referentieparameter gebruikt. En als er geen bestaat, krijg je een foutmelding.
De categorie rvalue-expressies omvat alle xvalue- en prvalue-expressies, en alleen die expressies.
De standaard bibliotheekfunctie std::move
bestaat om een niet-rvalue-expressie expliciet om te zetten in een rvalue. Meer in het bijzonder verandert het de uitdrukking in een xwaarde, omdat het, zelfs als het eerder een identiteitloze voorwaardelijke uitdrukking was, door het als een parameter door te geven aan std::move
, het identiteit krijgt (de parameternaam van de functie) en een xwaarde wordt.
Stel je de volgende situatie voor:
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
heeft een constructor die een enkele parameter van het type std::string&&
, gewoonlijk een "constructor verplaatsen" genoemd. De waardecategorie van de expressie str
is echter geen rvalue (met name een lvalue), dus deze kan die constructor-overbelasting niet aanroepen. In plaats daarvan roept het de const std::string&
overload aan, de constructor van de kopie.
Regel 3 verandert dingen. De retourwaarde van std::move
is een T&&
, waarbij T
het basistype is van de doorgegeven parameter. Dus std::move(str)
retourneert std::string&&
. Een functieaanroep waarvan de retourwaarde een rvalue-referentie is, is een rvalue-expressie (met name een xwaarde), dus deze kan de constructor van std::string
aanroepen. Na regel 3 is str
verplaatst van (wiens inhoud is nu niet gedefinieerd).
Regel 4 geeft een tijdelijke door aan de toewijzingsoperator van std::string
. Dit heeft een overbelasting waarvoor een std::string&&
. De uitdrukking std::string("new value")
is een rvalue-expressie (met name een prvalue), dus deze kan die overbelasting aanroepen. Het tijdelijke wordt dus verplaatst naar str
, waarbij de niet-gedefinieerde inhoud wordt vervangen door specifieke inhoud.
Regel 5 maakt een benoemde rvalue-referentie genaamd str_ref
die verwijst naar str
. Dit is waar waardecategorieën verwarrend worden.
Zie, terwijl str_ref
een str_ref
is naar std::string
, is de str_ref
van de uitdrukking str_ref
geen waarde. Het is een lvalue-uitdrukking. Ja werkelijk. Hierdoor kan men de constructor van std::string
niet aanroepen met de uitdrukking str_ref
. Regel 6 kopieert daarom de waarde van str
in test3
.
Om het te verplaatsen, zouden we std::move
opnieuw moeten gebruiken.