C++11 [1] [2] vagy ISO/IEC 14882:2011 [3] (a szabvány kidolgozása során C++0x kódneve volt [4] [5] ) – a szabvány új verziója a C++ nyelvi szabvány a korábban érvényes ISO /IEC 14882:2003 helyett. Az új szabvány kiegészítéseket tartalmaz a nyelv magjához és a szabványos könyvtár kiterjesztését, beleértve a TR1 nagy részét - kivéve talán a speciális matematikai függvények könyvtárát. A szabványok új verziói néhány más C++ szabványosítási dokumentummal együtt megjelennek az ISO C++ bizottság honlapján [6] . C++ programozási példák
A programozási nyelvek képességeinek fokozatos fejlesztése zajlik (jelenleg a C++11 után a következő szabványos bővítmények jelentek meg: C++14, C++17, C++20). Ez a folyamat elkerülhetetlenül kompatibilitási problémákat okoz a meglévő kóddal. Az N3290 nemzetközi szabvány végleges tervezetének C.2 függeléke [diff.cpp03] leír néhány inkompatibilitást a C++11 és a C++03 között.
Mint már említettük, a változtatások a C++ magot és annak szabványos könyvtárát is érintik.
A jövőbeni szabvány egyes szakaszainak kidolgozásakor a bizottság számos szabályt alkalmazott:
Figyelmet fordítanak a kezdőkre, akik mindig a programozók többségét teszik ki. Sok kezdő nem törekszik a C ++ ismereteinek elmélyítésére, és csak szűk specifikus feladatokon dolgozik [7] . Ezen túlmenően, tekintettel a C++ sokoldalúságára és széleskörű használatára (beleértve az alkalmazások és a programozási stílusok sokféleségét), még a szakemberek is új programozási paradigmákkal találkozhatnak .
A bizottság elsődleges feladata a C++ nyelv magjának fejlesztése. A kernel jelentősen javult, többszálú támogatással bővült, javult az általános programozás támogatása , egységesítették az inicializálást, és dolgoztak a teljesítmény javításán.
A kényelem kedvéért a kernel szolgáltatásai és módosításai három fő részre oszlanak: teljesítményjavítások, kényelmi fejlesztések és új funkciók. Az egyes elemek több csoportba is tartozhatnak, de csak egy - a legmegfelelőbb - leírására kerül sor.
Ezeket a nyelvi összetevőket a memória többletterhelésének csökkentése vagy a teljesítmény javítása érdekében vezették be.
Ideiglenes objektumhivatkozások és áthelyezési szemantikaA C++ szabvány szerint egy kifejezés kiértékeléséből származó ideiglenes objektum átadható függvényeknek, de csak állandó hivatkozással ( const & ). A függvény nem tudja megállapítani, hogy az átadott objektum ideiglenesnek és módosíthatónak tekinthető-e (egy ilyen hivatkozással is átadható const objektum nem módosítható (legálisan)). Ez nem probléma az olyan egyszerű struktúráknál, mint a complex, de az összetett típusoknál, amelyek memóriafoglalást-felszabadítást igényelnek, egy ideiglenes objektum megsemmisítése és egy állandó létrehozása időigényes lehet, míg a mutatókat egyszerűen átadhatja közvetlenül.
A C++11 egy új típusú hivatkozást vezet be , az rvalue referenciát . Deklarációja : type && . Az új túlterhelés-feloldási szabályok lehetővé teszik, hogy különböző túlterhelt függvényeket használjon a nem állandó ideiglenes objektumokhoz, amelyeket rértékek jelölnek, valamint az összes többi objektumhoz. Ez az innováció lehetővé teszi az úgynevezett mozgásszemantika megvalósítását .
Például std::vector egy egyszerű burkoló egy C-tömb körül, és egy változó, amely tárolja a méretét. A másoláskonstruktor std::vector::vector(const vector &x)létrehoz egy új tömböt, és másolja az információkat; az átviteli konstruktor std::vector::vector(vector &&x)egyszerűen kicserélheti a hosszt tartalmazó mutatókat és változókat.
Példa a hirdetésre.
sablon < osztály T > osztályvektor _ { vektor ( const vektor & ); // Konstruktor (lassú) vektor másolása ( vektor && ); // Konstruktor átvitele ideiglenes objektumból (gyors) vektor & operátor = ( const vektor & ); // Szabályos hozzárendelés (lassú) vektor & operátor = ( vektor && ); // Ideiglenes objektum áthelyezése (gyors) void foo () & ; // Csak elnevezett objektumon működő függvény (lassú) void foo () && ; // Csak ideiglenes objektumhoz működő függvény (gyors) };Az ideiglenes hivatkozásokhoz számos minta kapcsolódik, amelyek közül a két legfontosabb a és . Az első egy szabályos nevű objektumot ideiglenes hivatkozássá tesz: moveforward
// std::move sablon példa void bar ( std :: string && x ) { statikus std :: stringsomeString ; _ someString = std :: move ( x ); // az x=string& függvényen belül, ezért a második lépés a mozgatási hozzárendelés meghívásához } std :: stringy ; _ bár ( std :: mozgás ( y )); // Az első lépés a string-et string&&-vé alakítja a hívósávhozA sablont csak a metaprogramozásban használják, explicit template paramétert igényel (két megkülönböztethetetlen túlterhelése van), és két új C++ mechanizmushoz kapcsolódik. Az első a linkragasztás: , majd . Másodszor, a fenti bar() függvénynek szüksége van egy ideiglenes objektumra a külső oldalon, de belül az x paraméter egy közönséges nevű (lvalue) a tartaléknak, ami lehetetlenné teszi a string& paraméter automatikus megkülönböztetését a string&& paramétertől. Egy közönséges nem sablon függvényben a programozó elhelyezheti a move(t) vagy nem, de mi a helyzet a sablonnal? forwardusing One=int&&; using Two=One&;Two=int&
// példa a sablon használatára std::forward class Obj { std :: stringfield ; _ sablon < classT > _ Obj ( T && x ) : mező ( std :: előre < T > ( x )) {} };Ez a konstruktor lefedi a szokásos (T=string&), másolás (T=const string&) és mozgatási (T=string) túlterheléseket referencia ragasztással. A forward pedig nem csinál semmit, vagy kibővül std::move-ra a T típusától függően, és a konstruktor másolja, ha másolatról van szó, és mozog, ha mozgásról van szó.
Általános állandó kifejezésekA C++-ban mindig is az állandó kifejezések fogalma volt. Így az olyan kifejezések, mint a 3+4 , mindig ugyanazokat az eredményeket adták vissza anélkül, hogy mellékhatásokat okoztak volna. A konstans kifejezések önmagukban kényelmes módot biztosítanak a C++ fordítók számára a fordítási eredmény optimalizálására. A fordítók az ilyen kifejezések eredményeit csak fordításkor értékelik ki, és a már kiszámított eredményeket tárolják a programban. Így az ilyen kifejezések csak egyszer kerülnek kiértékelésre. Van néhány olyan eset is, amikor a nyelvi szabvány állandó kifejezések használatát írja elő. Ilyen esetek lehetnek például külső tömbök vagy enumértékek meghatározásai.
A fenti kód illegális a C++-ban, mert a GiveFive() + 7 technikailag nem egy fordítási időben ismert állandó kifejezés. A fordító éppen nem tudja, hogy a függvény ténylegesen egy állandót ad vissza futási időben. Ennek a fordítói érvelésnek az az oka, hogy ez a függvény befolyásolhatja egy globális változó állapotát, meghívhat egy másik, nem állandó futásidejű függvényt, és így tovább.
A C++11 bevezeti a constexpr kulcsszót , amely lehetővé teszi a felhasználó számára, hogy megbizonyosodjon arról, hogy egy függvény vagy egy objektumkonstruktor fordítási időállandót ad vissza. A fenti kód átírható így:
constexpr int GiveFive () { return 5 ;} int valami_érték [ GiveFive () + 7 ]; // 12 egész számból álló tömb létrehozása; megengedett a C++11Ez a kulcsszó lehetővé teszi a fordító számára, hogy megértse és ellenőrizze, hogy a GiveFive állandót ad- e vissza.
A constexpr használata nagyon szigorú korlátozásokat ír elő a függvény műveleteire:
A szabvány korábbi verziójában csak integer vagy enum típusú változók használhatók konstans kifejezésekben. A C++11 nyelvben ez a korlátozás megszűnik azoknál a változóknál, amelyek definícióját a constexpr kulcsszó előzi meg:
constexpr double accelerationOfGravity = 9,8 ; constexpr double moonGravity = accelerationOfGravity / 6 ;Az ilyen változókat már implicit módon a const kulcsszó jelöli . Csak konstans kifejezések eredményeit vagy az ilyen kifejezések konstruktorait tartalmazhatják.
Ha a felhasználó által definiált típusokból állandó értékeket kell létrehozni, akkor az ilyen típusú konstruktorok a constexpr segítségével is deklarálhatók . A konstans kifejezés-konstruktort, a konstans függvényekhez hasonlóan, szintén meg kell határozni az aktuális fordítási egységben való első használat előtt. Egy ilyen konstruktornak üres testtel kell rendelkeznie, és egy ilyen konstruktornak csak konstansokkal kell inicializálnia a típusának tagjait.
Változások az egyszerű adatok meghatározásábanA szabványos C++-ban csak azok a struktúrák tekinthetők egyszerű régi adattípusnak ( POD), amelyek megfelelnek egy bizonyos szabályrendszernek. Jó okunk van elvárni, hogy ezeket a szabályokat kiterjesszék, hogy több típust is POD-nak tekintsenek. Az ezeknek a szabályoknak megfelelő típusok használhatók C-kompatibilis objektumréteg-megvalósításban, azonban a C++03 ezen szabályok listája túlságosan korlátozó.
A C++11 számos szabályt lazít az egyszerű adattípusok meghatározásával kapcsolatban.
Egy osztály akkor tekinthető egyszerű adattípusnak, ha triviális , szabványos elrendezésű ( standard-layout ) , és ha az összes nem statikus adattag típusa is egyszerű adattípus.
A triviális osztály olyan osztály, amely:
A standard elhelyezésű osztály az az osztály, amely:
A szabványos C++-ban a fordítónak példányosítania kell egy sablont, amikor találkozik annak teljes szakosodásával egy fordítási egységben. Ez jelentősen megnövelheti a fordítási időt, különösen akkor, ha a sablon ugyanazokkal a paraméterekkel példányosodik nagyszámú fordítási egységben. Jelenleg nem lehet megmondani a C++-nak, hogy ne legyen példányosítás.
A C++11 bevezette a külső sablonok ötletét. A C++-nak már van egy szintaxisa, amely megmondja a fordítónak, hogy egy sablont egy bizonyos ponton példányosítani kell:
sablon osztály std :: vektor < MyClass > ;A C++ nem képes megakadályozni, hogy a fordító egy sablont példányosítson a fordítási egységben. A C++11 egyszerűen kiterjeszti ezt a szintaxist:
extern template class std :: vektor < MyClass > ;Ez a kifejezés azt mondja a fordítónak , hogy ne példányosítsa a sablont ebben a fordítási egységben.
Ezek a funkciók a nyelv egyszerűbb használatát hivatottak megkönnyíteni. Lehetővé teszik a típusbiztonság erősítését, a kódduplikáció minimalizálását, a kóddal való visszaélések megnehezítését és így tovább.
Inicializálási listákAz inicializálási listák fogalma a C++-ba a C-ből származik. Az ötlet az, hogy egy struktúra vagy tömb létrehozható úgy, hogy argumentumlistát adunk át abban a sorrendben, ahogyan a struktúra tagjai definiálva vannak. Az inicializálási listák rekurzívak, ami lehetővé teszi, hogy struktúrák tömbjeihez és beágyazott struktúrákat tartalmazó struktúrákhoz használják.
struct objektum { úszó először ; int másodperc ; }; Objektum skalár = { 0,43f , 10 }; // egy objektum, első=0.43f és második=10 Object anArray [] = {{ 13.4f , 3 }, { 43.28f , 29 }, { 5.934f , 17 }}; // három objektum tömbjeAz inicializálási listák nagyon hasznosak a statikus listákhoz, és akkor, ha egy struktúrát egy adott értékre szeretne inicializálni. A C++ konstruktorokat is tartalmaz, amelyek tartalmazhatják az objektumok inicializálásának általános munkáját. A C++ szabvány lehetővé teszi inicializálási listák használatát struktúrákhoz és osztályokhoz, feltéve, hogy azok megfelelnek a Plain Old Data (POD) definíciójának. A nem POD osztályok nem használhatnak inicializálási listákat az inicializáláshoz, beleértve a szabványos C++ konténereket, például vektorokat.
A C++11 társította az inicializálási listák fogalmát és egy std::initializer_list nevű sablonosztályt . Ez lehetővé tette a konstruktorok és más függvények számára, hogy paraméterként inicializálási listákat kapjanak. Például:
osztály SequenceClass { nyilvános : SequenceClass ( std :: inicializáló_lista < int > lista ); };Ez a leírás lehetővé teszi egy SequenceClass létrehozását egész számok sorozatából az alábbiak szerint:
SequenceClass someVar = { 1 , 4 , 5 , 6 };Ez bemutatja, hogyan működik egy speciális konstruktor egy inicializálási listához. Az ilyen konstruktorokat tartalmazó osztályokat az inicializálás során speciális módon kezeljük (lásd alább ).
Az std::initializer_list<> osztály a C++11 Standard Library-ben van definiálva. Ennek az osztálynak az objektumait azonban csak a C++11 fordító tudja statikusan létrehozni a {} zárójeles szintaxis használatával. A lista létrehozása után másolható, ez azonban referenciaként történik. Az inicializálási lista állandó: sem tagjai, sem adatai nem módosíthatók a létrehozás után.
Mivel az std::initializer_list<> egy teljes értékű típus, nem csak konstruktorokban használható. A közönséges függvények argumentumként használhatják a begépelt inicializálási listákat, például:
void FunctionName ( std :: inicializáló_lista < float > lista ); FunctionName ({ 1.0f , -3.45f , -0.4f });A szabványos tárolók a következőképpen inicializálhatók:
std :: vektor < std :: string > v = { " xyzzy" , "plugh" , "abracadabra " }; std :: vektor < std :: string > v { " xyzzy" , "plugh" , "abracadabra " }; Általános inicializálásA C++ szabvány számos problémát tartalmaz a típusinicializálással kapcsolatban. A típusok inicializálásának többféle módja van, és nem mindegyik vezet ugyanarra az eredményre. Például egy inicializáló konstruktor hagyományos szintaxisa függvénydeklarációnak tűnhet, és fokozottan ügyelni kell arra, hogy a fordító ne hibásan elemezze azt. Csak aggregált típusok és POD típusok inicializálhatók aggregált inicializálókkal (a fajta SomeType var = {/*stuff*/};).
A C++11 olyan szintaxist biztosít, amely lehetővé teszi az inicializálás egyetlen formájának használatát minden típusú objektumhoz az inicializálási lista szintaxisának kiterjesztésével:
struct BasicStruct { int x ; dupla y ; }; struct AltStruct { AltStruct ( int x , dupla y ) : x_ ( x ), y_ ( y ) {} privát : int x_ ; dupla y_ ; }; BasicStruct var1 { 5 , 3.2 }; AltStruct var2 { 2 , 4.3 };A var1 inicializálása pontosan ugyanúgy működik, mint az aggregátumok inicializálása, azaz minden objektum inicializálása a megfelelő érték kimásolásával történik az inicializálási listáról. Ha szükséges, implicit típuskonverziót alkalmazunk. Ha a kívánt átalakítás nem létezik, a forráskód érvénytelennek minősül. A var2 inicializálása során a konstruktor meghívásra kerül.
Lehetséges így kódot írni:
struct IdString { std :: karakterláncnév ; _ int azonosító ; }; IdString GetString () { return { "SomeName" , 4 }; // Vegye figyelembe az explicit típusok hiányát }Az általános inicializálás nem helyettesíti teljesen a konstruktor inicializálási szintaxisát. Ha egy osztálynak van olyan konstruktora, amely egy inicializálási listát ( TypeName(initializer_list<SomeType>); ) vesz argumentumként, akkor az elsőbbséget élvez a többi objektum-létrehozási lehetőséggel szemben. Például a C++11-ben az std::vector tartalmaz egy konstruktort, amely egy inicializálási listát vesz argumentumként:
std :: vektor < int > theVec { 4 };Ez a kód egy konstruktorhívást eredményez, amely egy inicializálási listát vesz argumentumként, nem pedig egy egyparaméteres konstruktort, amely egy adott méretű tárolót hoz létre. A konstruktor meghívásához a felhasználónak a szabványos konstruktorhívási szintaxist kell használnia.
Típus következtetésA szabványos C++-ban (és C-ben) a változó típusát kifejezetten meg kell adni. A sablontípusok és a sablon metaprogramozási technikák megjelenésével azonban egyes értékek típusa, különösen a függvény visszatérési értékei nem határozhatók meg könnyen. Ez nehézségekhez vezet a közbenső adatok változókban való tárolásában, néha szükséges lehet egy adott metaprogramozási könyvtár belső szerkezetének ismerete.
A C++11 két módszert kínál ezeknek a problémáknak a enyhítésére. Először is, egy kifejezetten inicializálható változó definíciója tartalmazhatja az auto kulcsszót . Ennek eredményeként létrejön egy, az inicializálási érték típusának megfelelő változó:
auto someStrangeCallableType = std :: bind ( & SomeFunction , _2 , _1 , someObject ); auto otherVariable = 5 ;A someStrangeCallableType típus lesz az a típus, amelyet a sablonfüggvény konkrét megvalósítása visszaad std::bindaz adott argumentumokhoz. Ezt a típust a fordító könnyen meghatározza a szemantikai elemzés során, de a programozónak némi kutatást kell végeznie a típus meghatározásához.
Az otherVariable típus is jól definiált, de a programozó is könnyen meghatározhatja. Ez a típus int , ugyanaz, mint egy egész szám.
Ezenkívül a decltype kulcsszó használható egy kifejezés típusának meghatározására fordításkor . Például:
int someInt ; decltype ( someInt ) otherIntegerVariable = 5 ;A decltype használata a leghasznosabb az auto -val együtt , mivel az autoként deklarált változó típusát csak a fordító ismeri. Ezenkívül a decltype használata nagyon hasznos lehet olyan kifejezésekben, amelyek operátortúlterhelést és sablonspecializációt használnak.
autokódredundancia csökkentésére is használható. Például ahelyett, hogy:
for ( vektor < int >:: const_iterator itr = myvec . cbegin (); itr != myvec . cend (); ++ itr )a programozó ezt írhatja:
for ( auto itr = myvec . cbegin (); itr != myvec . cend (); ++ itr )A különbség különösen akkor válik szembetűnővé, ha egy programozó nagyszámú különböző tárolót használ, bár még mindig van egy jó módszer a redundáns kód csökkentésére - a typedef.
A decltype jellel jelölt típus eltérhet az auto által kikövetkeztetett típustól .
#include <vektor> int main () { const std :: vektor < int > v ( 1 ); auto a = v [ 0 ]; // írja be a - int decltype ( v [ 0 ]) b = 1 ; // type b - const int& (visszatérési érték // std::vector<int>::operator[](size_type) const) auto c = 0 ; // írja be a c - int auto d = c ; // típus d - int decltype ( c ) e ; // típus e - int, a c nevű entitás típusa decltype (( c )) f = c ; // az f típus int&, mert (c) egy lvvalue decltype ( 0 ) g ; // a g típus int, mivel a 0 egy rérték } For-loop egy gyűjteménybenA szabványos C++ -ban a gyűjtemény elemei közötti iteráció sok kódot igényel . Egyes nyelvek, például a C# , rendelkeznek olyan lehetőségekkel, amelyek " foreach " utasítást biztosítanak , amely automatikusan végigfut a gyűjtemény elemein az elejétől a végéig. A C++11 hasonló lehetőséget mutat be. A for utasítás megkönnyíti az iterációt az elemek gyűjteményén:
int my_array [ 5 ] = { 1 , 2 , 3 , 4 , 5 }; for ( int & x : my_array ) { x *= 2 ; }Ez a for forma, amelyet angolul "range-based for"-nak hívnak, a gyűjtemény minden elemét meglátogatja. Ez vonatkozik a C tömbökre , inicializáló listákra és minden olyan típusra , amely funkcióval rendelkezik begin()és iterátorokatend() ad vissza . A szabványos könyvtár minden olyan tárolója , amely rendelkezik kezdet/vég párral, a gyűjtemény for utasításával fog működni.
Egy ilyen ciklus például C-szerű tömbökkel is működni fog, mert A C++11 mesterségesen vezeti be a számukra szükséges pszeudo-módszereket (kezdet, vége és néhány más).
// a klasszikus tömb tartomány alapú bejárása int arr1 [] = { 1 , 2 , 3 }; for ( auto el : arr1 ); Lambda függvények és kifejezésekA szabványos C++-ban például, amikor a szokásos C++ könyvtári algoritmusokat használjuk a sort and find , gyakran szükség van predikátum függvények meghatározására az algoritmus hívási helyének közelében. A nyelvben egyetlen mechanizmus létezik erre: a függvényosztály definiálása (a függvényen belül definiált osztály példányának átadása algoritmusoknak tilos (Meyers, Effective STL)). Ez a módszer gyakran túl redundáns és bőbeszédű, és csak megnehezíti a kód olvasását. Ráadásul a függvényekben definiált osztályokra vonatkozó szabványos C++ szabályok nem teszik lehetővé, hogy sablonokban használják őket, és így ellehetetlenítik a használatát.
A probléma kézenfekvő megoldása az volt, hogy lehetővé tették a lambda-kifejezések és a lambda-függvények meghatározását C++11-ben. A lambda függvény definíciója a következő:
[]( int x , int y ) { return x + y ; }Ennek a névtelen függvénynek a visszatérési típusa decltype(x+y) . A visszatérési típus csak akkor hagyható el, ha a lambda függvény alakja . Ez a lambda függvény méretét egyetlen kifejezésre korlátozza. return expression
A visszatérési típus kifejezetten megadható, például:
[]( int x , int y ) -> int { int z = x + y ; visszatér z ; }Ez a példa egy z ideiglenes változót hoz létre egy köztes érték tárolására. A normál funkciókhoz hasonlóan ez a köztes érték nem marad meg a hívások között.
A visszatérési típus teljesen elhagyható, ha a függvény nem ad vissza értéket (vagyis a visszatérési típus érvénytelen )
Lehetőség van a lambda függvénnyel azonos hatókörben meghatározott változókra való hivatkozások használatára is. Az ilyen változók halmazát általában zárásnak nevezik . A lezárások meghatározása és felhasználása a következők szerint történik:
std :: vektor < int > someList ; int összesen = 0 ; std :: for_each ( someList . begin (), someList . end (), [ & total ]( int x ) { összesen += x ; }); std :: cout << összesen ;Ez megjeleníti a lista összes elemének összegét. A teljes változó a lambda-függvényzár részeként kerül tárolásra. Mivel a total veremváltozóra hivatkozik, megváltoztathatja az értékét.
A lokális változók záróváltozói a & hivatkozási szimbólum használata nélkül is megadhatók , ami azt jelenti, hogy a függvény átmásolja az értéket. Ez arra kényszeríti a felhasználót, hogy deklarálja a helyi változóra való hivatkozás vagy másolás szándékát.
A garantáltan hatókörükben végrehajtott lambda-függvényeknél lehetőség van az összes veremváltozó használatára anélkül, hogy kifejezetten hivatkozni kellene rájuk:
std :: vektor < int > someList ; int összesen = 0 ; std :: for_each ( someList . begin (), someList . end (), [ & ]( int x ) { összesen += x ; });A megvalósítási módszerek belsőleg változhatnak, de a lambda-függvénytől elvárás, hogy egy mutatót tároljon annak a függvénynek a veremére, amelyben létrehozták, ahelyett, hogy egyedi veremváltozóhivatkozásokon működne.
Ha helyette [&]használja [=], akkor az összes használt változó átmásolódik, lehetővé téve a lambda függvény használatát az eredeti változókon kívül.
Az alapértelmezett átviteli mód az egyes változók listájával is kiegészíthető. Például, ha a legtöbb változót hivatkozással, egyet pedig érték szerint kell átadnia, használhatja a következő konstrukciót:
int összesen = 0 ; int érték = 5 ; [ & , érték ]( int x ) { összesen += ( x * érték ); } ( 1 ); //(1) hívja meg a lambda függvényt 1 értékkelEz azt eredményezi , hogy a teljes összeget referenciaként, az értéket pedig az érték szerint adja át.
Ha egy lambda függvény egy osztálymetódusban van definiálva, akkor azt az osztály barátjának tekintjük. Az ilyen lambda függvények hivatkozhatnak egy osztály típusú objektumra, és hozzáférhetnek annak belső mezőihez:
[]( SomeType * typePtr ) { typePtr -> SomePrivateMemberFunction (); }Ez csak akkor működik, ha a lambda függvény hatóköre egy SomeType osztálymetódus .
Az aktuális metódussal kölcsönhatásba lépő objektum ezen mutatójával végzett munka speciális módon valósul meg. Ezt kifejezetten meg kell jelölni a lambda függvényben:
[ this ]() { this -> SomePrivateMemberFunction (); }Egy űrlap [&]vagy [=]egy lambda függvény használatával ez automatikusan elérhetővé válik.
A lambda függvények típusa megvalósításfüggő; ennek a típusnak a neve csak a fordító számára elérhető. Ha egy lambda függvényt kell átadnia paraméterként, akkor annak sablon típusúnak kell lennie, vagy az std::function használatával kell tárolni . Az auto kulcsszó lehetővé teszi a lambda függvény helyi mentését:
auto myLambdaFunc = [ this ]() { this -> SomePrivateMemberFunction (); };Ezenkívül, ha a függvény nem vesz fel argumentumokat, akkor ()kihagyhatja:
auto myLambdaFunc = []{ std :: cout << "hello" << std :: endl ; }; Alternatív függvény szintaxisNéha szükség van egy függvénysablon megvalósítására, amely olyan kifejezést eredményez, amelynek ugyanolyan típusú és értékkategóriája van, mint bármely más kifejezésnek.
template < typename LHS , typename RHS > RETURN_TYPE AddingFunc ( const LHS & lhs , const RHS & rhs ) // mi legyen a RETURN_TYPE? { return lhs + rhs ; }Annak érdekében, hogy az AddingFunc(x, y) kifejezés ugyanolyan típusú és értékkategóriájú legyen, mint az lhs + rhs kifejezés, ha adott x és y argumentum , a következő definíció használható a C++11-ben:
sablon < típusnév LHS , típusnév RHS > decltype ( std :: declval < const LHS &> () + std :: declval < const RHS &> ()) AddingFunc ( állandó LHS és lhs , konst RHS és jobb oldali ) { return lhs + rhs ; }Ez a jelölés kissé körülményes, és jó lenne az lhs és az rhs használata az std::declval<const LHS &>() és az std::declval<const RHS &>() helyett. A következő verzióban azonban
template < typename LHS , typename RHS > decltype ( lhs + rhs ) AddingFunc ( const LHS & lhs , const RHS & rhs ) // Nem érvényes C++11 { return lhs + rhs ; }ember által olvashatóbb, a decltype operandusban használt lhs és rhs azonosítók nem jelölhetik a később deklarált opciókat. A probléma megoldására a C++11 új szintaxist vezet be a függvények deklarálására, amelynek végén visszatérési típus van:
sablon < típusnév LHS , típusnév RHS > auto AddingFunc ( const LHS & lhs , const RHS & rhs ) -> decltype ( lhs + rhs ) { return lhs + rhs ; }Meg kell azonban jegyezni, hogy az alábbi általánosabb AddingFunc megvalósításban az új szintaxis nem tesz jót a tömörségnek:
sablon < típusnév LHS , típusnév RHS > auto AddingFunc ( LHS && lhs , RHS && rhs ) -> decltype ( std :: előre < LHS > ( lhs ) + std :: előre < RHS > ( rhs )) { return std :: előre < LHS > ( lhs ) + std :: előre < RHS > ( rhs ); } sablon < típusnév LHS , típusnév RHS > auto AddingFunc ( LHS && lhs , RHS && rhs ) -> decltype ( std :: declval < LHS > () + std :: declval < RHS > ()) // ugyanaz a hatás, mint az std::forward fent { return std :: előre < LHS > ( lhs ) + std :: előre < RHS > ( rhs ); } sablon < típusnév LHS , típusnév RHS > decltype ( std :: declval < LHS > () + std :: declval < RHS > ()) // ugyanaz a hatás, mint a típus beírása a végére AddingFunc ( LHS && lhs , RHS && rhs ) { return std :: előre < LHS > ( lhs ) + std :: előre < RHS > ( rhs ); }Az új szintaxis egyszerűbb deklarációkban és deklarációkban használható:
struct SomeStruct { auto FuncName ( int x , int y ) -> int ; }; auto SomeStruct :: FuncName ( int x , int y ) -> int { vissza x + y _ }A " " kulcsszó autohasználata ebben az esetben csak a visszatérési típus késői jelzését jelenti, és nincs összefüggésben annak automatikus következtetésével.
Objektumkonstruktorok fejlesztéseA szabványos C++ nem teszi lehetővé, hogy egy osztálykonstruktort ugyanabban az osztályban egy másik konstruktorból hívjanak meg; minden konstruktornak teljesen inicializálnia kell az osztály összes tagját, vagy meg kell hívnia az osztály metódusait. Egy osztály nem állandó tagjai nem inicializálhatók azon a helyen, ahol ezeket a tagokat deklarálják.
A C++11 megszabadul ezektől a problémáktól.
Az új szabvány lehetővé teszi az egyik osztálykonstruktor meghívását egy másikból (az úgynevezett delegálás). Ez lehetővé teszi olyan konstruktorok írását, amelyek más konstruktorok viselkedését használják anélkül, hogy duplikált kódot vezetnének be.
Példa:
class SomeType { int szám ; nyilvános : SomeType ( int új_szám ) : szám ( új_szám ) {} SomeType () : SomeType ( 42 ) {} };A példából látható, hogy az SomeTypeargumentumok nélküli konstruktor meghívja ugyanannak az osztálynak a konstruktorát egy egész argumentummal, hogy inicializálja a változót number. Hasonló hatás érhető el, ha ennek a változónak a deklarációjában 42-es kezdőértéket adunk meg.
class SomeType { int szám = 42 ; nyilvános : SomeType () {} explicit SomeType ( int új_szám ) : szám ( új_szám ) {} };Bármely osztálykonstruktor 42-re inicializál number, ha maga nem rendel hozzá más értéket.
A Java , a C# és a D példák olyan nyelvekre, amelyek szintén megoldják ezeket a problémákat .
Megjegyzendő, hogy ha a C++03-ban egy objektumot teljesen létrejöttnek tekintünk, amikor a konstruktora befejezte a végrehajtást, akkor a C++11-ben legalább egy delegáló konstruktor végrehajtása után a többi konstruktor dolgozik teljesen felépített objektum. Ennek ellenére a származtatott osztály objektumai csak az alaposztályok összes konstruktorának végrehajtása után lesznek megszerkesztve.
A virtuális funkciók explicit helyettesítése és véglegességeLehetséges, hogy egy virtuális metódus aláírása megváltozott az alaposztályban, vagy kezdetben helytelenül lett beállítva a származtatott osztályban. Ilyen esetekben az adott metódus a származtatott osztályban nem írja felül a megfelelő metódust az alaposztályban. Tehát ha a programozó nem változtatja meg megfelelően a metódus aláírását az összes származtatott osztályban, akkor előfordulhat, hogy a metódus nem lesz megfelelően meghívva a program végrehajtása során. Például:
struct Base { virtual void some_func (); }; struct Származtatott : Base { void sone_func (); };Itt egy származtatott osztályban deklarált virtuális függvény neve hibásan van írva, így az ilyen függvény nem írja felül a -t Base::some_func, és ezért nem hívja meg polimorf módon az alap alobjektumra mutató mutatón vagy hivatkozáson keresztül.
A C++11 lehetőséget ad ezeknek a problémáknak a fordítási időben történő nyomon követésére (a futási idő helyett). A visszamenőleges kompatibilitás érdekében ez a funkció opcionális. Az új szintaxis alább látható:
B szerkezet { virtual void some_func (); virtual void f ( int ); virtual void g () const ; }; D1 struktúra : nyilvános B { void sone_func () override ; // hiba: érvénytelen függvénynév void f ( int ) override ; // OK: felülírja ugyanazt a függvényt az alaposztályban virtual void f ( long ) override ; // hiba: paramétertípus hibás virtual void f ( int ) const override ; // hiba: function cv-qualification mismatch virtual int f ( int ) override ; // hiba: return type mismatch virtual void g () const final ; // OK: felülírja ugyanazt a függvényt az alaposztályban virtual void g ( long ); // OK: új virtuális függvény }; struktúra D2 : D1 { virtual void g () const ; // hiba: kísérlet a végső függvény lecserélésére };A virtuális függvény specifikációjának jelenléte finalazt jelenti, hogy a további cseréje lehetetlen. Ezenkívül a végső specifikátorral definiált osztály nem használható alaposztályként:
struct F final { int x , y ; }; struct D : F // hiba: a végső osztályokból való öröklés nem megengedett { int z ; };A overrideés azonosítóknak finalcsak bizonyos helyzetekben van különleges jelentése. Más esetekben normál azonosítóként használhatók (például egy változó vagy függvény neveként).
Null pointer állandóA C 1972-es megjelenése óta a konstans 0 egy egész szám és egy nullamutató kettős szerepét tölti be. A C nyelvben rejlő kétértelműség kezelésének egyik módja a makró NULL, amely jellemzően a ((void*)0)vagy helyettesítést hajtja végre 0. A C++ ebben a tekintetben különbözik a C-től, és csak 0egy nulla mutató használatát teszi lehetővé állandóként. Ez rossz interakcióhoz vezet a funkció túlterhelésével:
void foo ( char * ); void foo ( int );Ha a makró NULLa következőképpen van definiálva 0(ami gyakori a C++-ban), akkor a sor foo(NULL);hívást fog eredményezni foo(int), nem foo(char *)pedig úgy, ahogy azt a kód gyors áttekintése sugallja, ami szinte biztosan nem a programozó szándéka.
A C++11 egyik újdonsága egy új kulcsszó a null pointer állandó leírására - nullptr. Ez a konstans típusú std::nullptr_t, amely implicit módon konvertálható bármely mutató típusává, és összehasonlítható bármely mutatóval. Az implicit átalakítás integrált típusba nem megengedett, kivéve a bool. A szabvány eredeti javaslata nem tette lehetővé a logikai értékre való implicit konverziót, de a szabványos szerkesztési csoport a hagyományos mutatótípusokkal való kompatibilitás érdekében engedélyezte az ilyen átalakításokat. A javasolt megfogalmazást 2008 júniusában egyhangú szavazással módosították [1] .
A visszafelé kompatibilitás érdekében a konstans 0nullmutatóként is használható.
char * pc = nullptr ; // igaz int * pi = nullptr ; // true bool b = nullptr ; // jobb. b=hamis. int i = nullptr ; // hiba foo ( nullptr ); // hívja a foo(char *), nem a foo(int);Gyakran azok a konstrukciók, ahol a mutató garantáltan üres, egyszerűbbek és biztonságosabbak, mint a többi – így túlterhelheti a -t . nullptr_t
osztály hasznos teher ; class SmartPtr { SmartPtr () = alapértelmezett ; SmartPtr ( nullptr_t ) {} // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< explicit SmartPtr ( hasznos terhelés * aData ) : fData ( aData ) {} // konstruktorok és op= másolása ~ SmartPtr () { delete fData ; } privát : Hasznos teher * fData = nullptr ; } SmartPtr getPayload1 () { return nullptr ; } // A SmartPtr(nullptr_t) túlterhelés meghívásra kerül. Erősen gépelt enumokA szabványos C++-ban az enumok nem típusbiztosak. Valójában egész számokkal ábrázolják őket, annak ellenére, hogy maguk a felsorolások típusai különböznek egymástól. Ez lehetővé teszi a különböző enumokból származó két érték összehasonlítását. Az egyetlen lehetőség, amit a C++03 kínál az enumok védelmére, az az, hogy nem konvertálja implicit módon egy enum egész számát vagy elemeit egy másik enum elemeivé. Ezenkívül a memóriában való megjelenítése (egész típus) megvalósításfüggő, ezért nem hordozható. Végül a felsorolási elemeknek közös a hatóköre, ami lehetetlenné teszi az azonos nevű elemek létrehozását különböző felsorolásban.
A C++11 ezeknek az enumoknak egy speciális osztályozását kínálja, a fenti hátrányoktól mentesen. Az ilyen felsorolások leírására deklarációt használnak (ezt szinonimájaként enum classis használhatjuk ):enum struct
enum class felsorolás { Val1 , Val2 , Val3 = 100 , Val4 , /* = 101 */ };Az ilyen felsorolás típusbiztos. Az osztály enum elemei nem konvertálhatók implicit egész számokká. Ennek következtében az egész számokkal való összehasonlítás sem lehetséges (a kifejezés Enumeration::Val4 == 101fordítási hibát eredményez).
Az osztályfelsorolás típusa mostantól független a megvalósítástól. Alapértelmezés szerint, mint a fenti esetben, ez a típus int, de más esetekben a típus manuálisan állítható be az alábbiak szerint:
enum class Enum2 : unsigned int { Val1 , Val2 };Az enum tagok körét az enum név köre határozza meg. Az elemnevek használatához meg kell adni az osztály enum nevét. Tehát például az érték Enum2::Val1meg van határozva, de az érték Val1 nincs megadva.
Ezenkívül a C++11 lehetőséget kínál a hatókör és az alapul szolgáló típusok kifejezett meghatározására a normál enumokhoz:
enum Enum3 : előjel nélküli hosszú { Val1 = 1 , Val2 };Ebben a példában az enum elemnevek az enum térben vannak definiálva (Enum3::Val1), de a visszafelé kompatibilitás érdekében az elemnevek a közös hatókörben is elérhetők.
A C++11-ben is lehetséges az enumok előre deklarálása. A C++ korábbi verzióiban ez nem volt lehetséges, mert egy enum mérete az elemeitől függött. Az ilyen nyilatkozatok csak akkor használhatók, ha a felsorolás mérete (kifejezetten vagy implicit módon) meg van adva:
enum Enum1 ; // érvénytelen C++ és C++11 esetén; mögöttes típus nem határozható meg enum Enum2 : unsigned int ; // igaz a C++11-re, az alapul szolgáló típus kifejezetten meghatározott enum osztály Enum3 ; // igaz a C++11-re, az alapul szolgáló típus int enum osztály Enum4 : unsigned int ; // igaz a C++11-re. enum Enum2 : unsigned short ; // érvénytelen a C++11 esetében, mert az Enum2 korábban más mögöttes típussal volt deklarálva SzögzárójelekA szabványos C++ elemzők mindig a ">>" karakterkombinációt határozzák meg jobb shift operátorként. A sablonparaméterekben (ha be vannak ágyazva) a záró szögletes zárójelek közötti szóköz hiánya szintaktikai hibaként kezelendő.
A C++11 ebben az esetben javítja az értelmező viselkedését, így a több derékszögű zárójelet a rendszer a sablon argumentumlistáinak lezáróként értelmezi.
A leírt viselkedés zárójelekkel rögzíthető a régi megközelítés javára.
sablon < osztály T > osztály Y { /* ... */ }; Y < X < 1 >> x3 ; // Helyes, ugyanaz, mint "Y<X<1> > x3;". I < X < 6 >> 1 >> x4 ; // Szintaktikai hiba. Be kell írnia, hogy "Y<X<(6>>1)>> x4;".Mint fentebb látható, ez a változtatás nem teljesen kompatibilis az előző szabvánnyal.
Explicit konverziós operátorokA C++ szabvány a kulcsszót explicitmódosítóként biztosítja az egyparaméteres konstruktorokhoz, így az ilyen konstruktorok nem működnek implicit konverziós konstruktorként. Ez azonban semmilyen módon nem érinti a tényleges konverziós operátorokat. Például egy intelligens mutató osztály tartalmazhat operator bool()egy normál mutatót utánzót. Egy ilyen operátor például így hívható: if(smart_ptr_variable)(az ág akkor kerül végrehajtásra, ha a mutató nem nulla). A probléma az, hogy egy ilyen operátor nem véd más váratlan átalakítások ellen. Mivel a típus boolaritmetikai típusként van deklarálva a C++-ban, lehetséges az implicit konverzió bármilyen egész típusra vagy akár lebegőpontos típusra, ami viszont váratlan matematikai műveletekhez vezethet.
A C++11 nyelvben a kulcsszó explicita konverziós operátorokra is vonatkozik. A konstruktorokhoz hasonlóan véd a váratlan implicit konverziók ellen. Azok a helyzetek azonban, amikor a nyelv kontextus szerint logikai típust vár (például feltételes kifejezésekben, ciklusokban és logikai operátor-operandusokban), explicit konverziónak minősülnek, és az explicit logikai konverziós operátort közvetlenül hívják meg.