C++0x

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 sablonok használatakor szóközt kellett közéjük tenni. Az új szabvány szerint azonban sablonok használatakor ez a jelentése élvez elsőbbséget. Ezt a viselkedést zárójelezéssel lehet megváltoztatni. A következő példában a hibás sorban a SomeType<1>-et fogja a fordító típusnak értelmezni, a harmadik sor mutatja a példányosítást helyesen.

template<bool bTest> SomeType; std::vector<SomeType<1>2>> x1 ; // Hibás std::vector<SomeType<(1>2)>> x1 ;

Kulcsszavak

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

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[], const char32_t[]. A következőképpen hozhatóak létre:

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

A literálokba Unicode karaktereket is tudunk rakni, 16 bites kódolásban \u, 32 bites kódolásban \U előtaggal:

u"\u2018"

Új hasznos funkció a raw literál. Raw literálban használhatjuk a " jelet is, a literál kezdetét R"DELIMITER[, végét ]DELIMITER" karaktersorozattal jelezhetjük.

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

Az új C++ nyelvben lehet literált definiálni. Az új literált az utótagja alapján lehet azonosítani (amely kötelezően aláhúzás jellel kezdődik), továbbá definiálni kell egy operátort:

OutputType operator "" _Suffix(const char *literal_string); OutputType someVariable = "1234"_Suffix;

Ez természetesen működik az új sztring literálokkal is:

OutputType operator "" _Suffix(const char16_t *literal_string, size_t num_chars); OutputType someVariable = u"1234"_Suffix;

Standard függvények

A régi C++-ban azon objektumoknak, amiknek nem volt meghatározva, a fordító meghatározott bizonyos funkciókat, mint például default constructor, copy constructor, destructor. Ezeket a felhasználó felüldefiniálhatta. Ezen kívül volt lehetőség néhány globális operátor (pl operator=, vagy a new operátor) felüldefiniálására is.

Viszont ezen kívül más lehetősége nincs a programozónak, és ez igen kevés befolyást jelent ezen funkciók felett. Az új szabvány lehetőséget biztosít, hogy explicit módon használja, vagy megtiltsa ezeket a funkciókat.

Például megadhatjuk a default constructort explicit módon a következőképpen:

OutputType operator "" _Suffix(const char16_t *literal_string, size_t num_chars); OutputType someVariable = u"1234"_Suffix;
struct SomeType { SomeType() = default; SomeType(OtherType value); };

Ezen kívül lehetőség van direkt módon megtiltani egyes funkciókat:

struct NonCopyable { NonCopyable & operator=(const NonCopyable&) = delete; NonCopyable(const NonCopyable&) = delete; NonCopyable() = default; };

Nézzük meg a következő példát

struct NoDouble { void f(int i); void f(double) = delete; };

A fenti strukturában az f függvény meghívása double típussal el lesz utasítva a fordító által, ahelyett, hogy automatikusan int-é konvertálódna. A fenti példa általánosítását láthatjuk a következő példában, ahol egy függvény hívását megtiltjuk minden egyes típusra, kivéve az int-re.

struct OnlyInt { void f(int i); template<class T> void f(T) = delete; };

Static assert

A static assert a következőképpen néz ki:

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(sizeof(long) >= 8, "64-bit code generation required for this library."); struct S { X m1; Y m2; }; static_assert(sizeof(S)==sizeof(X)+sizeof(Y),"unexpected padding in S");

Osztályon belüli adattagok inicializálása

A régi C++-ban csupán statikus, konstans egész típusú adattagok inicializálhatóak az osztályon belül, méghozzá konstans kifejezéssel. Vagyis:

int var = 7; class X { static const int m1 = 7; // ok const int m2 = 7; // hiba: nem statikus static int m3 = 7; // hiba: nem konstans static const int m4 = var; // hiba: nem konstans kifejezést használunk static const string m5 = "odd"; // hiba: nem egész típusú // ... };

Ennek alapján az alapötlet a C++0x-ben az, hogy engedélyezzék a nem statikus adattagok ilyen módon történő inicializálását is.

class A { public: int a = 7; };

Ez a következővel ekvivalens:

class A { public: int a; A() : a(7) {} };

Ezzel megspórolhatunk egy csomó gépelést, de az igazi előnyük akkor látszik, amikor olyan osztályokat tekintünk, amelyek több konstruktorral rendelkeznek. Nézzük a következő példát:

class A { public: A(): a(7), b(5), hash_algorithm("MD5"), s("Constructor run") {} A(int a_val) : a(a_val), b(5), hash_algorithm("MD5"), s("Constructor run") {} A(D d) : a(7), b(g(d)), hash_algorithm("MD5"), s("Constructor run") {} int a, b; private: HashingFunction hash_algorithm; std::string s; // ez reprezentálja az állapotát az objektumnak };

Az új szabvány lehetőséget ad arra, hogy az előző példát átírjuk a következőre:

class A { public: A(): a(7), b(5) {} A(int a_val) : a(a_val), b(5) {} A(D d) : a(7), b(g(d)) {} int a, b; private: HashingFunction hash_algorithm{"MD5"}; std::string s{"Constructor run"}; };

Amikor egy adattag egy konstruktorral és egy inicializálóvfüggvénnyel is inicializálva van, akkor csak a konstruktoré lesz érvényes, szóval írhatjuk akár a következőt is:

class A { public: A() {} A(int a_val) : a(a_val) {} A(D d) : b(g(d)) {} int a = 7; int b = 5; private: HashingFunction hash_algorithm{"MD5"}; std::string s{"Constructor run"}; };

Rvalue elemek

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. Ezekutá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++0x 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.

Unio

A C++ korai változatában egy olyan adattag, amely konstruktorral, destruktorral, vagy értékadással rendelkezett, nem lehetett unió tagja.

union U { int m1; complex<double> m2; // hiba: complex -nek van konstruktora string m3; // hiba: string has a serious invariant };

Ezek után

U u; // melyik konstruktor? u.m1 = 1; // hozzáférés int adattaghoz string s = u.m3; // hiba: olvasás string adattagból

Az új C++0x szabvány megváltoztatja a korlátozásokat.

Például:

union U1 { int m1; complex<double> m2; // ok }; union U2 { int m1; string m3; // ok };

Hibásnak tűnhet, de az új korlátozások segítenek.

U1 u; // ok u.m2 = {1,2}; // ok: hozzáférés complex adattagjához U2 u2; // hiba: a string destructor miatt U destruktora törlődött U2 u3 = u2; // hiba: a string = miatt az U = törlődött

Vagyis U2 használhatatlan, hacsak be nincs ágyazva egy struct-ba, ami képes figyelni, melyik adattag van használva. Vagyis:

class Widget { private: enum class Tag { point, number, text } type; // diszkrimináns union { // reprezentáció point p; // point-nak van konstruktora int i; string s; // stringnek van default konstruktora, destruktora }; // ... widget& operator=(const widget& w) // szükséges a string változó miatt { if (type==Tag::text && w.type==Tag::text) { s = w.s; // szokásos string értékadás return *this; } if (type==Tag::text) s.~string(); // destroy switch (type=w.type) { case Tag::point: p = w.p; break; // normal copy case Tag::number: i = w.i; break; case Tag::text: new(&s)(w.s); break; // placement new } return *this; } };