C++11

Nyelvi elemek

Szintaxis

Az új szabvány egy fontos pontban módosítja a C++ szintaxisát. A régi C++-ban a >> karaktersorozat minden esetben a shift right operátort jelentette, így egymásba ágyazott template-ek deklarálásakor szóközt kellett a két záró > közé tenni. Az új szabvány szerint azonban sablonok használatakor ez a jelentése élvez elsőbbséget. Bizonyos esetekben így is syntax error-t kapunk, ezt a viselkedést zárójelezéssel lehet megváltoztatni:

template<bool Test> SomeType; std::vector<SomeType<1>2>> x1 ; // Hibás: SomeType<true> 2> -k vektora std::vector<SomeType<(1>2)>> x1 ; // Helyes: SomeType<false> -k vektora

Kulcsszavak

A következő új kulcsszavak kerülnek bevezetésre: constexpr, auto, decltype, nullptr, static_assert.

Sztring literálok

A standard C++-ban kétféle string literált használhattunk ezelőtt.

A fentiek közül egyik sem támogatja a unicode string literálokat.

Az új C++-ban három Unicode kódolás támogatása is megtalálható, használhatóak UTF-8, UTF-16 illetve UTF-32 kódolású sztring literálok is. Ezek típusa rendre const char[], const char16_t[] és const char32_t[] lesz. A következőképpen hozhatóak létre:

u8"UTF-8" u"UTF-16" U"UTF-32"

A literálokba konkrét Unicode karaktereket is be tudunk szúrni 16 bites kódolásban a \u, 32 bites kódolásban a \U előtaggal:

u"Ez egy Unicode karakter:\u2018"

Új hasznos funkció a raw literál. A literál kezdetét R"DELIMITER(, végét )DELIMITER" karaktersorozattal jelezhetjük, ahol a DELIMITER egy tetszőleges általunk választott (maximum 16 hosszú) karaktersorozat. A két zárójel közötti rész lesz maga a sztring, amely tartalmazhat " és / anélkül, hogy escape karaktert kellene használnunk.

R"(Ez egy RAW sztring. \ " )" R"delimiter(Ez is. \ " )delimiter"

Felhasználó által definiált literálok

Az új C++ nyelvben lehet saját literálokat definiálni. Az új literált az utótagja alapján lehet azonosítani, definiálása pedig a "" operátor definiálásával történik:

Distance operator "" km(unsigned long long x) { return Distance(x); } Distance someVariable = 1234km;

Egész, vagy lebegőpontos alaptípusok esetén az operátorok deklarációjának létezik "cooked" (az érték átadása unsigned long long-ként vagy long double-ként) és "uncooked" változata is (az érték átadása char*-ként):

OutputType operator "" _suffix(unsigned long long); OutputType operator "" _suffix(long double); OutputType operator "" _suffix(const char*);

Sztringekre a következő formák léteznek a sztringliterál fajtája szerint:

OutputType operator "" _suffix(const char * string_values, size_t num_chars); OutputType operator "" _suffix(const wchar_t * string_values, size_t num_chars); OutputType operator "" _suffix(const char16_t * string_values, size_t num_chars); OutputType operator "" _suffix(const char32_t * string_values, size_t num_chars); OutputType someVariable = u"1234"_suffix; // a 3. hívódik meg

Static assert

Fordítás idejű ellenőrzésekre az új static_assert kulcsszó használható (szemben az assert makróval, amely runtime ellenőrzésekhez használatos):

static_assert(expression,string);

A fordító kiértékeli a kifejezést (expression), és kiírja a string üzenetet, ha a kiértékelés eredménye hamis. (assertion failed) Például:

static_assert ((GREEKPI > 3.14) && (GREEKPI < 3.15), "GREEKPI is inaccurate!"); template<class T> struct Check { static_assert (sizeof(int) <= sizeof(T), "T is not big enough!"); };

Rvalue elemek, move szemantika

A régi C++-ban nem konstans referenciákat lehetett balértékekhez (lvalue) kötni, konstansokat pedig bal-, és jobbértékekhez egyaránt. Viszont nem volt semmi, amit hozzá lehetett kapcsolni egy nem konstans jobbértékhez. Tekintsük a következő példát:

void incr(int& a) { ++a; } int i = 0; incr(i); // i értéke 1 lesz incr(0); // hiba: 0 nem lvalue

Ha incr(0) engedélyezve lenne, akkor egyrészt egy senki által nem használt temporáris változó meg lenne növelve eggyel, és ami sokkal rosszabb, ezentúl 0 értéke 1 lenne. Ez elsőre furcsán hangzik, de volt egy hasonló bug a korai Fortran fordítókban. Ezek után tekintsük a következő cserélőfüggvényt

template<class T> swap(T& a, T& b) { T tmp(a); // a-ból 2 példányunk van a = b; // b-ből 2 példányunk van b = tmp; // tmp-ből (a-ból) 2 példányunk van }

Ha T típus valami bonyolult típus, akkor a fenti swap függvény egy elég költséges függvény lehet. Sőt, igazából nekünk egyáltalán nincs szükségünk egyetlen másolatra sem, mi csak egyszerűen mozgatgatni akarjuk egy kicsit az a, b, és tmp változókat.

Az új szabvány lehetőséget ad arra, hogy úgynevezett "move constructor"-t, illetve "move assignment"-et definiáljunk, hogy az egyes elemeket másolás helyett mozgassunk.

template<class T> class vector { // ... vector(const vector&); // copy constructor vector(vector&&); // move constructor vector& operator=(const vector&); // copy assignment vector& operator=(vector&&); // move assignment };

A fenti példában látható && jelöli az úgynevezett rvalue referenciákat. Ezeket mind bal-, mind jobbértékekhez hozzáköthetjük.

X a; X f(); X& r1 = a; // r1-et hozzákötjük a-hoz (balérték) X& r2 = f(); // hiba: f() jobbérték, nem lehet hozzákötni r2-höz X&& rr1 = f(); // helyes: hozzáköti rr1-et X&& rr2 = a; // szintén helyes

A példához hasonlóan pl s1=s2 esetében használni a move assignment-et lényegében azt jelenti, hogy nem csinálunk egy egy másolatot s2 karaktereiből, hanem ehelyett hagyjuk, hogy s1 a sajátjaiként kezelje azokat, és valahogy töröljük s1 régi karaktereit.

Tekintsük a következő példát:

template<class T> void swap(T& a, T& b) { T tmp = move(a); // érvényteleníti a-t a = move(b); // érvényteleníti b-t b = move(tmp); //érvényteleníti tmp-t }

move(x) jelentése: "úgy használhatod x-et, mintha jobbérték lenne".

A régi void swap(T& a, T& b) továbbra is balértékeket használ, de most, a C++11 lehetőséget ad számunkra a következőkre:

template<class T> void swap(T&& a, T& b); template<class T> void swap(T& a, T&& b); template<class T> void swap(T& a, T& b); vector<double> v = {1, 2, 3, 4, 5, 6 } swap(v,{});

A fenti kóddal felcserélhetünk egy balértéket, és egy jobbértéket.