C++11

Típusok, típuskonstrukciók

Elemi típusok

Az előző fejezetben láthattuk, hogy új típusok kerültek bevezetésre, az UTF-16 és UTF-32 karakterláncok tárolására: char16_t és char32_t. Emellett a a legtöbb fordító által már régóta támogatott long long is a szabványba került.

long long int

A C99-ben bevezetett long long int típust most már a C++ is fogja támogatni. A szabvány szerint legalább 64 biten ábrázolódik, és legalább akkora, mint a long int.

Felsorolási típus

A C++ újradefiniálja felsorolási típust is. Az eddigi felsorolási típus ugyanis nem volt típusbiztos. Ez a C++03-ban kezdett megváltozni: azóta az enum típusok nem implicit konvertálódnak egymás között, illetve egészről. Nem definiálja a szabvány azt az egész típust sem, amelyen a felsorolási típusok ábrázolódtak, a méretük a fordítótól függ. Nem lehetett továbbá egy névtérben két olyan felsorolási típus, amelyek azonos nevű értéket tartalmaznak, ugyanis az értékek nevei a típust tartalmazó névtérbe kerülnek.

Az új szabvány szerint a tároláshoz használt egész típus megadható (alapértelmezett az int) és saját névterük van:

enum class Enumeration: unsigned short { Val1, Val2, Val3 = 100, Val4 /* = 101 */, };

A kompatibilitás megtartása miatt az enum kulcsszó továbbra is használható, valamint a definiált nevek a tartalmazó névtérbe is bekerülnek, de saját névtér is tartozik hozzá. Itt is megadható a tárolási típus.

enum Enumeration: unsigned short { Val1, Val2, Val3 = 100, Val4 /* = 101 */, };

Mivel az enum tárolási mérete a benne lévő adatoktól függ, nem volt forward deklarálható. Mivel most már megadható (illetve az enum class esetében ismert), lehetővé válik a forward deklarációjuk:

enum Enum1; // Hibás, nem ismert a mérete. enum Enum2 : unsigned int; // Helyes. enum class Enum3; // Helyes, int-ben tárolódik. enum class Enum4: unsigned int; // Helyes.

Explicit típuskonverziós operátorok

A szabványos C++ az explicit kulcsszót használja annak jelölésére, hogy egy konstruktor nem használható implicit konverzióra. Az új szabvány megengedi, hogy a kulcsszót konverziós operátorokon is használjuk.

A régi C++ implicit és explicit konstruktorok használatára nyújt lehetőséget.

struct S { S(int); }; // "szokásos constructor" implicit konverzió S s1(1); // ok S s2 = 1; // ok void f(S); f(1); // ok (de néha csúnya meglepetés is lehet - mi van, ha S vector?) struct E { explicit E(int); }; // explicit constructor E e1(1); // ok E e2 = 1; // hiba! void f(E); f(1); // hiba

Azonban nem a konstruktor az egyetlen lehetőség a konverzióra. Ha nincs lehetőségünk megváltoztatni egy osztályt, akkor definiálhatunk egy konverziós operátort egy másik osztályból. Például:

struct S { S(int) { } /* ... */ }; struct SS { int m; SS(int x) :m(x) { } operator S() { return S(m); } // mert S -nek nincs S(SS); -ja }; SS ss(1); S s1 = ss; // ok; mint egy implicit constructor S s2(ss); // ok ; mint egy implicit constructor void f(S); f(ss); // ok; mint egy implicit constructor

Sajnálatos módon nincs explicit konverziós operátor. Ezt a hiányosságot a C++11 kijavítja, és megengedi, hogy a konverziós operátorok explicitek legyenek. Például:

struct S { S(int) { } }; struct SS { int m; SS(int x) :m(x) { } explicit operator S() { return S(m); } // mert S -nek nincs S(SS); -ja }; SS ss(1); S s1 = ss; // hiba; mint egy explicit constructor S s2(ss); // ok ; mint egy explicit constructor void f(S); f(ss); // hiba; mint egy explicit constructor

Konstans kifejezések

Az új C++ bevezeti a konstans kifejezés típusokat is a constexpr kulcsszóval. Ezzel a kulcsszóval jelölhetjük, ha egy függvényünk fordítási időben kiszámítható. Ez használható többek között tömbök definiálásakor (a tömb méretének fordítási időben ismertnek kell lennie C++-ban):

constexpr int GetFive() { return 5; } int some_value[GetFive() + 5];

Null pointer

1972-ben a C-ben a 0 konstansnak két jelentése volt, egyrészt jelentette a 0 számot, másrészt a null pointert. A 0 kettős jelentését úgy kezelték, hogy bevezették a NULL szimbólumot, amelyet az előfeldolgozó oldott fel, általában a ((void *)0) kifejezésre. Ez azonban C++ tervezésekor problémákat okozott, hiszen a void * mutatók nem implicit konvertálódnak más mutató típusokra, így a C++-ban ismét a 0 szimbólumot kellett használni null pointerként (tehát a NULL is erre helyettesítődött). Azonban nem oldódott meg minden probléma.

void foo(int); void foo(char *);

Ekkor a foo(NULL) kifejezés a foo(int) függvényt hívja, ami megtévesztő, és egyértelműen nem a programozó szándékát fejezi ki.

Az új C++ szabvány bevezeti a nullptr kulcsszót a null pointer jelölésére. A nullptr típusa olyan, hogy tetszőleges mutató típusra implicit konvertálódik, valamint tetszőleges mutatóval összehasonlítható, nem konvertálódik azonban egész típusokra, kivéve a bool-ra.

char *pc = nullptr; // OK int *pi = nullptr; // OK bool b = nullptr; // OK. b hamis int i = nullptr; // Error foo(nullptr); // foo(char *)-ot hívja, nem a foo(int)-et;

Típuskikövetkeztetés

A C++-ban részeredmények tárolása sokszor nehézkes, hiszen bonyolult típusok fordulhatnak elő benne, főleg a szabványos könyvtárt nagyrészt alkotó template-k esetén. A pontos típus a felhasználó számára sokszor nehezen határozható meg.

Az auto kulcsszót használva a típust a fordító kikövetkezteti az inicializációs kifejezés alapján. (Tehát a változó definiálásakor kötelező azt expliciten inicializálni!)

A következő két sor ugyanazt csinálja:

for (vector<int>::iterator itr = myvec.begin(); itr != myvec.end(); ++itr) for (auto itr = myvec.begin(); itr != myvec.end(); ++itr)

Hasonló módon használhatjuk decltype kulcsszót. Ez esetben fordítási időben kiértékelődik a zárójelben lévő kifejezés típusa, és az így kapott típus lesz a változó típusa:

int someInt; decltype(someInt) otherIntegerVariable = 5;

Pár további példa:

#include <vector> int main() { const std::vector<int> v(1); auto a = v[0]; // a típusa int decltype(v[0]) b = 1; // b típusa const int&, // ami az 'std::vector::operator[](size_type) const' visszatérési értéke auto c = 0; // c típusa int auto d = c; // d típusa int decltype(c) e; // e típusa int decltype((c)) f = c; // f típusa int&, mert (c) egy lvalue decltype(0) g; // g típusa int, mert 0 egy rvalue }

Ezzel megoldhatóvá válik egy régi dilemma a függvények visszatérési értékével kapcsolatban:

template decltype(U() + T()) add(U u, T t) { return u + t; }

Az új függvénydeklarációs szintaxissal ez még szebb és szélesebb körűen használhatóvá válik.

Unió

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++11 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; } };