Megjegyzés
Az oldalhoz való hozzáféréshez engedély szükséges. Megpróbálhat bejelentkezni vagy módosítani a címtárat.
Az oldalhoz való hozzáféréshez engedély szükséges. Megpróbálhatja módosítani a címtárat.
Ez a témakör bemutatja és ismerteti a C++-ban létező különböző értékkategóriákat (és az értékekre mutató hivatkozásokat):
- glvalue
- lvalue
- xlvalue
- prvalue
- rvalue
Bizonyára hallott már a lvaluákról és a revaluákról. De lehet, hogy nem gondol rájuk a jelen témakör által ismertetett kifejezésekben.
A C++-ban minden kifejezés olyan értéket ad vissza, amely a fent felsorolt öt kategória egyikéhez tartozik. A C++ nyelvnek – annak lehetőségeinek és szabályainak – vannak olyan aspektusai, amelyek megkövetelik ezeknek az értékkategóriáknak a megfelelő megértését, valamint az ezekre mutató hivatkozásokat. Ilyen szempontok például az érték címének felvétele, egy érték másolása, egy érték áthelyezése és egy érték továbbítása egy másik függvénybe. Ez a témakör nem foglalkozik az összes ilyen szempont részletességével, de alapvető információkat nyújt azok szilárd megértéséhez.
A jelen témakör információi a Stroustrup értékkategóriáinak az identitás és a movability két független tulajdonsága alapján történő elemzését tekintik [Stroustrup, 2013].
Egy lvalue identitással rendelkezik
Mit jelent, ha egy érték identitással rendelkezik? Ha rendelkezik (vagy felveheti) egy érték memóriacímét, és biztonságosan használhatja, akkor az érték identitással rendelkezik. Így többre is képes, mint az értékek tartalmának összehasonlítása – identitás alapján összehasonlíthatja vagy megkülönböztetheti őket.
Egy lvalue identitással rendelkezik. Mára már csak történeti érdekesség, hogy az „lvalue” szóban az „l” a „left” rövidítése (vagyis az értékadás bal oldala). A C++-ban egy érték jelenhet meg a hozzárendelés bal oldalán vagy jobb oldalán. Az "lvalue"-ban az "l" valójában nem segít megérteni és meghatározni, hogy mik azok. Csak azt kell megértenie, hogy az lvalue-nak nevezett érték identitással rendelkezik.
A lvalues kifejezések például a következők: egy elnevezett változó vagy állandó; vagy olyan függvény, amely egy hivatkozást ad vissza. Példák olyan kifejezésekre, amelyek nem értékértékek: ideiglenes; vagy egy érték szerint visszaadott függvény.
int& get_by_ref() { ... }
int get_by_val() { ... }
int main()
{
std::vector<byte> vec{ 99, 98, 97 };
std::vector<byte>* addr1{ &vec }; // ok: vec is an lvalue.
int* addr2{ &get_by_ref() }; // ok: get_by_ref() is an lvalue.
int* addr3{ &(get_by_ref() + 1) }; // Error: get_by_ref() + 1 is not an lvalue.
int* addr4{ &get_by_val() }; // Error: get_by_val() is not an lvalue.
}
Bár igaz állítás, hogy a lvalues identitással rendelkezik, ez az xvaluesre is igaz. A jelen témakör későbbi részében pontosan bemutatjuk, hogy mi is az xvalue . Egyelőre csak vegye figyelembe, hogy létezik egy glvalue nevű értékkategória (általánosított érték). A glvalue-k készlete mindkét lvalue (más néven klasszikus lvalues) és xvalues szuperhalmaza. Tehát, míg a "lvalue identitással rendelkezik" igaz, az identitással rendelkező dolgok teljes készlete a glvaluek készlete, ahogyan az ábrán látható.
Az rvalue mozgatható; az lvalue nem az
Vannak azonban olyan értékek, amelyek nem glvalue-k. Más szóval vannak olyan értékek, amelyekhez nem szerezhet be memóriacímet (vagy nem hivatkozhat rá érvényesként). A fenti kód példában néhány ilyen értéket láttunk.
A megbízható memóriacím hiánya hátránynak tűnik. De valójában az ilyen érték előnye, hogy áthelyezheti (ami általában olcsó), ahelyett, hogy lemásolja (ami általában drága). Az érték áthelyezése azt jelenti, hogy már nem azon a helyen van, ahol korábban volt. Ezért kerülni kell, hogy megpróbáljuk elérni a helyet, ahol korábban volt. A témakör hatókörén kívül esik egy érték áthelyezésének időpontjáról és módjáról szóló vita. Ebben a témakörben csak azt kell tudnunk, hogy egy ingó érték rvalue (vagy klasszikus rvalue) néven ismert.
Az "rvalue"-ban az "r" a "right" rövidítése (mint a feladat jobb oldalán). De a hozzárendeléseken kívül használhatja az rvaluákat és az rvaluákra mutató hivatkozásokat. Az „rvalue” szóban szereplő „r” tehát nem az, amire összpontosítani kell. Csak azt kell megértenie, hogy amit rvalue-nak nevezünk, az egy mozgatható érték.
Ezzel szemben a lvalue nem mozgatható, ahogy az ábrán is látható. Ha egy lvalue mozogna, az ellentmondana az lvalue definíciójának. És ez váratlan problémát jelentene azon kód számára, amely teljes joggal számít arra, hogy továbbra is hozzáférhet az lvalue-hoz.
Szóval nem lehet lvalue-t mozgatni. De van egyfajta glvalue (az identitással rendelkező dolgok készlete), amelyet áthelyezhet – ha tudja, hogy mit csinál (beleértve, hogy ügyeljen arra, hogy ne férhessen hozzá az áthelyezés után) – és ez az xvalue. A jelen témakör későbbi részében még egyszer áttekintjük ezt az ötletet, amikor áttekintjük az értékkategóriák teljes képét.
Rvalue-hivatkozások és referenciakötési szabályok
Ez a szakasz egy értékre mutató hivatkozás szintaxisát mutatja be. Meg kell várnunk, amíg egy másik témakör jelentős mértékben kezeli az áthelyezést és a továbbítást, de elegendő azt mondani, hogy a revalue-hivatkozások a problémák megoldásának szükséges részei. Mielőtt azonban megnéznénk az rvalue-referenciákat, előbb világosabban kell látnunk, mi is az a T&, amit korábban egyszerűen csak „referenciának” neveztünk. Ez valójában "lvalue (non-const) hivatkozás", amely egy olyan értékre utal, amelyre a hivatkozás felhasználója írhat.
template<typename T> T& get_by_lvalue_ref() { ... } // Get by lvalue (non-const) reference.
template<typename T> void set_by_lvalue_ref(T&) { ... } // Set by lvalue (non-const) reference.
Egy lvalue-hivatkozás köthet egy lvalue-hoz, de nem rvalue-hoz.
Ezután vannak lvalue const-hivatkozások (T const&), amelyek olyan objektumokra hivatkoznak, amelyekre a hivatkozás felhasználója nem tud írni (például állandó).
template<typename T> T const& get_by_lvalue_cref() { ... } // Get by lvalue const reference.
template<typename T> void set_by_lvalue_cref(T const&) { ... } // Set by lvalue const reference.
Az lvalue const-referencia egy lvalue-hez vagy egy rvalue-hoz kötődhet.
A(z) T típusú rvalue-ra való hivatkozás szintaxisa így írható: T&&. A revalue-hivatkozás egy ingó értékre utal – olyan értékre, amelynek tartalmát nem kell megőrizni a használat után (például ideiglenes). Mivel az egész lényege egy rvalue-hivatkozáshoz kötött érték áthelyezése (és ezáltal módosítása), a const és volatile minősítők (más néven cv-minősítők) nem vonatkoznak az rvalue-hivatkozásokra.
template<typename T> T&& get_by_rvalue_ref() { ... } // Get by rvalue reference.
struct A { A(A&& other) { ... } }; // A move constructor takes an rvalue reference.
A rvalue-referencia egy rvalue-hoz kötődik. Valójában a túlterhelés feloldása szempontjából a rvalue inkább rvalue-hivatkozáshoz, mint egy lvalue const-hivatkozáshoz kötődik. Az rvalue-referenciák azonban nem köthetők lvalue-hez, mert mint már említettük, az rvalue-hivatkozás egy olyan értékre utal, amelynek tartalmát feltételezzük, hogy nem kell megőriznünk (például egy áthelyezési konstruktor paraméterét).
Át is adhat egy rvalue-t, ahol egy értéken belüli argumentum várható, másolási konstrukción keresztül (vagy áthelyezési konstrukción keresztül, ha az rvalue xvalue).
A glvalue identitással rendelkezik; a prvalue nem
Ebben a szakaszban tudjuk, mi az identitás. És tudjuk, mi mozgatható és mi nem. De még nem neveztük el az identitással nem rendelkező értékek készletét. Ezt a készletet prvalue-nak vagy tiszta rvalue-nak nevezzük.
int& get_by_ref() { ... }
int get_by_val() { ... }
int main()
{
int* addr3{ &(get_by_ref() + 1) }; // Error: get_by_ref() + 1 is a prvalue.
int* addr4{ &get_by_val() }; // Error: get_by_val() is a prvalue.
}
Az értékkategóriák teljes képe
A fenti információkat és ábrákat csak egyetlen, nagy képpé kell egyesíteni.
glvalue (i)
A glvalue (általánosított lvalue) identitással rendelkezik. Az „i” rövidítésként használjuk a „van identitása” jelentésére.
lvalue (i&!m)
Az lvalue (egyfajta glvalue) identitással rendelkezik, de nem mozgatható. Ezek általában olyan értékek, amelyek olvashatók és módosíthatók, és amelyeket rendszerint hivatkozással vagy konstans hivatkozással, illetve — ha a másolásuk nem költséges — érték szerint adsz át. Az lvalue nem köthető rvalue-hivatkozáshoz.
xvalue (i&m)
Az xvalue-nak (a glvalue egyik fajtája, de egyben az rvalue egyik fajtája is) van identitása, és egyben mozgatható is. Ez lehet egy korábbi lvalue, amelyet úgy döntöttél, hogy áthelyezel, mert a másolása költséges, utána pedig ügyelsz arra, hogy ne férj hozzá. Az alábbiakban bemutatjuk, hogyan alakíthat egy lvalue-t xvalue-vá.
struct A { ... };
A a; // a is an lvalue...
static_cast<A&&>(a); // ...but this expression is an xvalue.
A fenti kód példájában még nem helyeztünk át semmit. Csupán egy xvalue-t hoztunk létre úgy, hogy egy lvalue-t egy meg nem nevezett rvalue-hivatkozásra adtunk. Továbbra is azonosítható a lvalue neve alapján; de xvalue-ként most már áthelyezni is lehet . Hogy miért költöztetjük át, és pontosan mit jelent ez az áthelyezés a gyakorlatban, az egy másik téma lesz. De ha ez segít, gondolhatsz úgy az „xvalue” szóban az „x”-re, hogy azt jelenti: „csak szakértőknek”. Egy lvalue xvalue-vá alakításával (azaz egyfajta rvalue-vá, ne feledjük) az érték ezután rvalue-referenciához köthetővé válik.
Íme két másik példa az xvalue-kra: egy név nélküli rvalue-referenciát visszaadó függvény meghívása, valamint egy xvalue tagjának elérése.
struct A { int m; };
A&& f();
f(); // This expression is an xvalue...
f().m; // ...and so is this.
prvalue (!i&m)
A prvalue (tiszta rvalue; egyfajta rvalue) nem rendelkezik identitással, de mozgatható. Ezek általában ideiglenesek, vagy egy érték alapján visszaadott függvény meghívásának eredménye, vagy bármely más kifejezés kiértékelésének eredménye, amely nem glvalue.
rvalue (m)
Az rvalue mozgatható. Az "m" rövidítést fogjuk használni a "mozgatható" kifejezéshez.
A rvalue-referenciák mindig egy rvalue-re hivatkoznak (olyan értékre, amelynek tartalmát feltételezzük, hogy nem kell megőrizni).
De egy rvalue-hivatkozás maga is rvalue? Egy névtelen rvalue-hivatkozás (mint amilyenek a fenti xvalue-kódpéldákban láthatók) xvalue, tehát igen, rvalue-nak számít. Inkább egy rvalue referenciafüggvény paraméteréhez, például egy áthelyezési konstruktorhoz van kötve. Ezzel szemben (és talán ellen-intuitív módon), ha egy rvalue hivatkozásnak van neve, akkor az abból a névből álló kifejezés egy lvalue. Ezért nem lehet rvalue referenciaparaméterhez kötni. Ezt azonban könnyű megtenni – csak adja vissza egy névtelen rvalue-hivatkozáshoz (xvalue).
void foo(A&) { ... }
void foo(A&&) { ... }
void bar(A&& a) // a is a named rvalue reference; so it's an lvalue.
{
foo(a); // Calls foo(A&).
foo(static_cast<A&&>(a)); // Calls foo(A&&).
}
A&& get_by_rvalue_ref() { ... } // This unnamed rvalue reference is an xvalue.
!i&!m
Az az érték, amely nem rendelkezik identitással, és nem mozgatható, az egyetlen kombináció, amiről még nem beszéltünk. De figyelmen kívül hagyhatjuk, mert ez a kategória nem hasznos ötlet a C++ nyelven.
A hivatkozások összecsukásának szabályai
Egy kifejezésben a több, azonos típusú referencia (egy lvalue-referencia egy lvalue-referenciára, vagy egy rvalue-referencia egy rvalue-referenciára) kioltja egymást.
-
A& &összecsukódikA&formára. -
A&& &&A&&alakúra csukható össze.
Egy kifejezésben több, egymástól eltérő referencia balérték-referenciává redukálódik.
- A(z)
A& &&elemA&elemmé csukódik össze. -
A&& &A&méretűre csukódik össze.
Továbbítási hivatkozások
Ez az utolsó szakasz ellentétben áll a már tárgyalt rvalue-hivatkozásokkal, a továbbítási hivatkozás eltérő fogalmával. A "továbbítási hivatkozás" kifejezés megalkotása előtt egyes emberek az "univerzális hivatkozás" kifejezést használták.
void foo(A&& a) { ... }
-
A&&egy rvalue referencia, ahogy láttuk. A const és a volatile nem alkalmazható az rvalue-hivatkozásokra. -
foocsak A típusú rvaluákat fogad el. - Az rvalue-referenciák (például
A&&) azért léteznek, hogy olyan túlterhelést lehessen írni, amely arra az esetre van optimalizálva, amikor egy ideiglenes objektumot (vagy más rvalue-t) adnak át.
template <typename _Ty> void bar(_Ty&& ty) { ... }
-
_Ty&&egy továbbítási hivatkozás. Attól függően, hogy mit ad át a(z)bar-nak, a(z) _Ty típus lehet const vagy nem const, a volatile vagy nem volatile minősítéstől függetlenül. -
barelfogadja a _Ty típusú lvalue-t vagy rvalue-t. - Az lvalue átadása hatására a továbbító referencia
_Ty& &&lesz, amely lvalue referenciává_Ty&omlik össze. - Egy rvalue átadása esetén a továbbítási hivatkozás
_Ty&&rvalue-referenciává válik. - A továbbító hivatkozások (például
_Ty&&) nem azért léteznek, hogy optimalizáljanak, hanem azért, hogy átvegyék azt, amit nekik átadsz, és átláthatóan, illetve hatékonyan továbbítsák. Valószínűleg csak akkor talál továbbítási hivatkozást, ha kódtárkódot ír (vagy tanulmányoz). Ilyen például egy gyári függvény, amely konstruktorargumentumokon továbbít.
Sources
- [Stroustrup, 2013] B. Stroustrup: The C++ Programming Language, Fourth Edition. Addison-Wesley. 2013.
Windows developer