C++11 [1] [2] nebo ISO/IEC 14882:2011 [3] (v procesu práce na standardu měl kódové označení C++0x [4] [5] ) — nová verze jazykový standard C++ namísto dříve platné ISO /IEC 14882:2003. Nový standard zahrnuje doplňky k jádru jazyka a rozšíření standardní knihovny, včetně většiny TR1 – snad kromě knihovny speciálních matematických funkcí. Nové verze norem spolu s některými dalšími normalizačními dokumenty C++ jsou zveřejněny na webu komise ISO C++ [6] . Příklady programování v C++
Programovací jazyky procházejí postupným rozvojem svých schopností (v současné době jsou po C++11 publikována standardní rozšíření: C++14, C++17, C++20). Tento proces nevyhnutelně způsobuje problémy s kompatibilitou se stávajícím kódem. Příloha C.2 [diff.cpp03] Final Draft International Standard N3290 popisuje některé nekompatibility mezi C++11 a C++03.
Jak již bylo zmíněno, změny se dotknou jak jádra C++, tak jeho standardní knihovny.
Při vývoji každé části budoucího standardu výbor použil řadu pravidel:
Pozornost je věnována začátečníkům, kteří budou vždy tvořit většinu programátorů. Mnoho začátečníků se nesnaží prohloubit své znalosti C++, omezují se na jeho použití při práci na úzkých specifických úkolech [7] . Navíc, vzhledem k všestrannosti C++ a šíři jeho použití (včetně různých aplikací a programovacích stylů), se i profesionálové mohou setkat s novými paradigmaty programování .
Primárním úkolem komise je vyvinout jádro jazyka C++. Jádro bylo výrazně vylepšeno, byla přidána podpora multithreadingu , vylepšena podpora generického programování , sjednocena inicializace a práce na zlepšení jeho výkonu.
Pro pohodlí jsou vlastnosti a změny jádra rozděleny do tří hlavních částí: vylepšení výkonu, vylepšení pohodlí a nové funkce. Jednotlivé prvky mohou patřit do několika skupin, ale budou popsány pouze v jedné - nejvhodnější.
Tyto jazykové komponenty jsou zavedeny za účelem snížení režie paměti nebo zlepšení výkonu.
Dočasné odkazy na objekty a přesunutí sémantikyPodle standardu C++ lze dočasný objekt vyplývající z vyhodnocení výrazu předat funkcím, ale pouze konstantním odkazem ( const & ). Funkce není schopna určit, zda lze předaný objekt považovat za dočasný a modifikovatelný (konstační objekt, který lze také předat takovým odkazem, nelze upravit (legálně)). To není problém pro jednoduché struktury, jako complexje , ale pro složité typy, které vyžadují alokaci-dealokaci paměti, může být zničení dočasného objektu a vytvoření trvalého časově náročné, zatímco ukazatele lze jednoduše předat přímo.
C++11 zavádí nový typ odkazu , odkaz rvalue . Jeho deklarace je: type && . Nová pravidla pro rozlišení přetížení vám umožňují používat různé přetížené funkce pro nekonstantní dočasné objekty, označené rvalues, a pro všechny ostatní objekty. Tato inovace umožňuje implementaci tzv. sémantiky pohybu .
Jedná se například std::vector o jednoduchý obal kolem C-pole a proměnné, která ukládá jeho velikost. Kopírovací konstruktor std::vector::vector(const vector &x)vytvoří nové pole a zkopíruje informace; konstruktor přenosu std::vector::vector(vector &&x)může jednoduše vyměňovat ukazatele a proměnné obsahující délku.
Příklad reklamy.
šablona < class T > class vector { vektor ( const vector & ); // Kopírování konstruktoru (pomalý) vektor ( vector && ); // Přenos konstruktoru z dočasného objektu (rychlý) vector & operator = ( const vector & ); // Běžné přiřazení (pomalé) vector & operator = ( vector && ); // Přesunutí dočasného objektu (rychle) void foo () & ; // Funkce, která funguje pouze na pojmenovaném objektu (pomalá) void foo () && ; // Funkce, která funguje pouze pro dočasný objekt (rychle) };S dočasnými odkazy je spojeno několik vzorů, z nichž dva nejdůležitější jsou a . První z nich dělá z běžného pojmenovaného objektu dočasnou referenci: moveforward
// std::move template example void bar ( std :: string && x ) { static std :: stringsomeString ; _ someString = std :: move ( x ); // uvnitř funkce x=string&, tedy druhý tah pro volání přiřazení pohybu } std :: stringy ; _ bar ( std :: pohyb ( y )); // první tah změní řetězec& na řetězec&& do lišty voláníŠablona se používá pouze v metaprogramování, vyžaduje explicitní parametr šablony (má dvě nerozlišitelná přetížení) a je spojena se dvěma novými mechanismy C++. První z nich je lepení odkazů: , pak . Za druhé, výše uvedená funkce bar() vyžaduje na vnější straně dočasný objekt, ale uvnitř je parametr x obyčejný pojmenovaný (lvalue) pro nouzový přechod, což znemožňuje automatické rozlišení parametru string& od parametru string&&. V běžné funkci bez šablony programátor může nebo nemusí vložit move(), ale co šablona? forwardusing One=int&&; using Two=One&;Two=int&
// příklad použití šablony std::forward class Obj { std :: stringfield ; _ šablona < classT > _ Obj ( T && x ) : pole ( std :: vpřed < T > ( x )) {} };Tento konstruktor pokrývá přetížení regulárního (T=řetězec&), kopírování (T=konst řetězce&) a přesunu (T=řetězec) pomocí lepení odkazů. A forward nedělá nic nebo se expanduje na std::move v závislosti na typu T a konstruktor zkopíruje, pokud se jedná o kopii, a přesune, pokud se jedná o přesun.
Obecné konstantní výrazyC++ měl vždy koncept konstantních výrazů. Výrazy jako 3+4 tedy vždy vrátily stejné výsledky, aniž by způsobily nějaké vedlejší účinky. Konstantní výrazy samy o sobě poskytují kompilátorům C++ pohodlný způsob, jak optimalizovat výsledek kompilace. Překladače vyhodnocují výsledky takových výrazů pouze v době kompilace a ukládají již vypočítané výsledky do programu. Takové výrazy se tedy vyhodnocují pouze jednou. Existuje také několik případů, kdy jazyková norma vyžaduje použití konstantních výrazů. Takovými případy mohou být například definice externích polí nebo hodnot enum.
Výše uvedený kód je v C++ nezákonný, protože GiveFive() + 7 není technicky konstantní výraz známý v době kompilace. Kompilátor v tu chvíli prostě neví, že funkce skutečně vrací konstantu za běhu. Důvodem této úvahy kompilátoru je to, že tato funkce může ovlivnit stav globální proměnné, volat jinou nekonstantní běhovou funkci a tak dále.
C++11 zavádí klíčové slovo constexpr , které uživateli umožňuje zajistit, aby buď funkce nebo konstruktor objektu vrátil konstantu v době kompilace. Výše uvedený kód by mohl být přepsán takto:
constexpr int GiveFive () { return 5 ;} int nějaká_hodnota [ GiveFive () + 7 ]; // vytvoří pole 12 celých čísel; povoleno v C++ 11Toto klíčové slovo umožňuje kompilátoru pochopit a ověřit, že GiveFive vrací konstantu.
Použití constexpr ukládá velmi přísná omezení na akce funkce:
V předchozí verzi standardu bylo možné v konstantních výrazech používat pouze proměnné typu integer nebo enum. V C++11 je toto omezení zrušeno pro proměnné, jejichž definici předchází klíčové slovo constexpr:
constexpr double zrychleniOfGravity = 9,8 ; constexpr double moonGravity = akceleraceOfGravity / 6 ;Takové proměnné jsou již implicitně považovány za označené klíčovým slovem const . Mohou obsahovat pouze výsledky konstantních výrazů nebo konstruktory takových výrazů.
Pokud je nutné zkonstruovat konstantní hodnoty z uživatelsky definovaných typů, konstruktory takových typů lze deklarovat také pomocí constexpr . Konstruktor konstantního výrazu, stejně jako konstantní funkce, musí být také definován před prvním použitím v aktuální kompilační jednotce. Takový konstruktor musí mít prázdné tělo a takový konstruktor musí inicializovat členy svého typu pouze s konstantami.
Změny v definici jednoduchých datVe standardním C++ lze pouze struktury, které splňují určitou sadu pravidel, považovat za prostý starý datový typ ( POD). Existují dobré důvody očekávat, že tato pravidla budou rozšířena tak, aby bylo více typů považováno za PODy. Typy, které splňují tato pravidla, lze použít v implementaci objektové vrstvy kompatibilní s C. Seznam těchto pravidel v C++03 je však příliš omezující.
C++11 uvolní několik pravidel týkajících se definice jednoduchých datových typů.
Třída je považována za jednoduchý datový typ, pokud je triviální , má standardní rozložení ( standard-layout ) a pokud jsou typy všech jejích nestatických datových členů také jednoduché datové typy.
Triviální třída je třída, která:
Třída se standardním umístěním je třída, která:
Ve standardním C++ musí kompilátor vytvořit instanci šablony, kdykoli narazí na svou plnou specializaci v překladové jednotce. To může výrazně prodloužit dobu kompilace, zvláště když je šablona konkretizována se stejnými parametry ve velkém počtu překladových jednotek. V současné době neexistuje způsob, jak říci C++, že by neměla existovat žádná instance.
C++11 představil myšlenku externích šablon. C++ již má syntaxi, která říká kompilátoru, že šablona by měla být vytvořena v určitém okamžiku:
šablona class std :: vector < MyClass > ;C++ postrádá schopnost zabránit kompilátoru vytvořit instanci šablony v překladové jednotce. C++ 11 jednoduše rozšiřuje tuto syntaxi:
extern template class std :: vector < MyClass > ;Tento výraz říká kompilátoru , aby nevytvářel instanci šablony v této překladové jednotce.
Tyto funkce mají usnadnit používání jazyka. Umožňují posílit bezpečnost typů, minimalizovat duplicitu kódu, ztížit zneužití kódu a tak dále.
Inicializační seznamyKoncept inicializačních seznamů přišel do C++ z C. Myšlenka je taková, že strukturu nebo pole lze vytvořit předáním seznamu argumentů ve stejném pořadí, v jakém jsou definováni členové struktury. Inicializační seznamy jsou rekurzivní, což umožňuje jejich použití pro pole struktur a struktury obsahující vnořené struktury.
struct objekt { plavat první ; int druhý ; }; Objekt skalární = { 0,43f , 10 }; // jeden objekt, s first=0.43f a second=10 Object anArray [] = {{ 13.4f , 3 }, { 43.28f , 29 }, { 5.934f , 17 }}; // pole tří objektůInicializační seznamy jsou velmi užitečné pro statické seznamy a když chcete inicializovat strukturu na konkrétní hodnotu. C++ také obsahuje konstruktory, které mohou obsahovat obecnou práci inicializace objektů. Standard C++ umožňuje použití inicializačních seznamů pro struktury a třídy za předpokladu, že odpovídají definici Plain Old Data (POD). Třídy jiné než PODD nemohou k inicializaci používat inicializační seznamy, včetně standardních kontejnerů C++, jako jsou vektory.
C++11 spojil koncept inicializačních seznamů a šablony třídy s názvem std::initializer_list . To umožnilo konstruktérům a dalším funkcím přijímat inicializační seznamy jako parametry. Například:
třída SequenceClass { veřejnost : SequenceClass ( std :: seznam_inicializátoru < int > seznam ); };Tento popis vám umožňuje vytvořit SequenceClass z posloupnosti celých čísel následovně:
SequenceClass someVar = { 1 , 4 , 5 , 6 };To ukazuje, jak funguje speciální druh konstruktoru pro inicializační seznam. S třídami obsahujícími takové konstruktory se během inicializace zachází zvláštním způsobem (viz níže ).
Třída std::initializer_list<> je definována ve standardní knihovně C++11. Objekty této třídy však mohou být staticky vytvořeny pouze kompilátorem C++11 pomocí syntaxe hranatých závorek {}. Seznam lze po vytvoření zkopírovat, bude to však kopírování po odkazu. Inicializační seznam je konstantní: po vytvoření nelze měnit jeho členy ani jejich data.
Protože std::initializer_list<> je plnohodnotný typ, lze jej použít ve více než jen konstruktorech. Běžné funkce mohou jako argument převzít zadané inicializační seznamy, například:
void NázevFunkce ( std :: seznam_inicializátoru < float > seznam ); Název funkce ({ 1.0f , -3.45f , -0.4f });Standardní kontejnery lze inicializovat takto:
std :: vector < std :: string > v = { "xyzzy" , "plugh" , "abracadabra" }; std :: vector < std :: string > v { "xyzzy" , "plugh" , "abracadabra" }; Obecná inicializaceStandard C++ obsahuje řadu problémů souvisejících s inicializací typu. Existuje několik způsobů inicializace typů a ne všechny vedou ke stejným výsledkům. Například tradiční syntaxe inicializačního konstruktoru může vypadat jako deklarace funkce a je třeba věnovat zvláštní pozornost tomu, aby ji kompilátor nezanalyzoval špatně. SomeType var = {/*stuff*/};Pomocí agregačních inicializátorů (druhu ) lze inicializovat pouze agregační typy a typy POD .
C++11 poskytuje syntaxi, která umožňuje použití jediné formy inicializace pro všechny druhy objektů rozšířením syntaxe inicializačního seznamu:
struct BasicStruct { int x ; dvojité y ; }; struct AltStruct { AltStruct ( int x , double y ) : x_ ( x ), y_ ( y ) {} soukromý : int x_ ; dvojité y_ ; }; BasicStruct var1 { 5 , 3.2 }; AltStruct var2 { 2 , 4,3 };Inicializace var1 funguje úplně stejně jako inicializace agregátů, to znamená, že každý objekt bude inicializován zkopírováním odpovídající hodnoty z inicializačního seznamu. V případě potřeby bude použita implicitní konverze typu. Pokud požadovaná transformace neexistuje, bude zdrojový kód považován za neplatný. Během inicializace var2 bude zavolán konstruktor.
Je možné napsat kód takto:
struct IdString { std :: stringname ; _ int identifikátor ; }; IdString GetString () { return { "NějakéJméno" , 4 }; // Všimněte si nedostatku explicitních typů }Obecná inicializace zcela nenahrazuje syntaxi inicializace konstruktoru. Pokud má třída konstruktor, který jako argument bere inicializační seznam ( TypeName(initializer_list<SomeType>); ), bude mít přednost před ostatními možnostmi vytváření objektů. Například v C++11 std::vector obsahuje konstruktor, který jako argument bere inicializační seznam:
std :: vector < int > theVec { 4 };Tento kód bude mít za následek volání konstruktoru, které vezme jako argument inicializační seznam, spíše než konstruktor s jedním parametrem, který vytvoří kontejner dané velikosti. K volání tohoto konstruktoru bude uživatel muset použít standardní syntaxi vyvolání konstruktoru.
Typový závěrVe standardním C++ (a C) musí být typ proměnné výslovně specifikován. S příchodem typů šablon a technik metaprogramování šablon však nelze typ některých hodnot, zejména návratových hodnot funkcí, snadno určit. To vede k potížím při ukládání mezilehlých dat do proměnných, někdy může být nutné znát vnitřní strukturu konkrétní metaprogramovací knihovny.
C++11 nabízí dva způsoby, jak tyto problémy zmírnit. Za prvé, definice explicitně inicializovatelné proměnné může obsahovat klíčové slovo auto . Výsledkem bude vytvoření proměnné typu inicializační hodnoty:
auto someStrangeCallableType = std :: bind ( & SomeFunction , _2 , _1 , someObject ); auto otherPromenna = 5 ;Typ someStrangeCallableType se stane typem, který konkrétní implementace funkce šablony vrátí std::bindpro dané argumenty. Tento typ bude snadno určen kompilátorem během sémantické analýzy, ale programátor by musel provést nějaký průzkum, aby typ určil.
Typ otherVariable je také dobře definovaný, ale může být stejně snadno definován programátorem. Tento typ je int , stejný jako celočíselná konstanta.
Kromě toho lze klíčové slovo decltype použít k určení typu výrazu v době kompilace . Například:
int someInt ; decltype ( someInt ) otherIntegerVariable = 5 ;Použití decltype je nejužitečnější ve spojení s auto , protože typ proměnné deklarované jako auto zná pouze kompilátor. Použití decltype může být také docela užitečné ve výrazech, které používají přetěžování operátorů a specializaci šablon.
autolze také použít ke snížení redundance kódu. Například místo:
for ( vector < int >:: const_iterator itr = myvec . cbegin ( ) ; itr != myvec . cend (); ++ itr )programátor umí napsat:
for ( auto itr = myvec . cbegin (); itr != myvec . cend (); ++ itr )Rozdíl je zvláště patrný, když programátor používá velké množství různých kontejnerů, i když stále existuje dobrý způsob, jak snížit nadbytečný kód - pomocí typedef.
Typ označený decltype se může lišit od typu odvozeného pomocí auto .
#include <vektor> int main () { const std :: vector < int > v ( 1 ); auto a = v [ 0 ]; // typ a - int decltype ( v [ 0 ]) b = 1 ; // typ b - const int& (návratová hodnota // std::vector<int>::operator[](typ_velikosti) const) auto c = 0 ; // napište c - int auto d = c ; // zadejte d - int decltype ( c ) e ; // typ e - int, typ entity s názvem c decltype (( c )) f = c ; // typ f je int& protože (c) je lvalue decltype ( 0 ) g ; // typ g je int, protože 0 je rvalue } Pro-smyčka přes kolekciVe standardním C++ vyžaduje iterace prvků kolekce hodně kódu . Některé jazyky, jako je C# , mají prostředky, které poskytují příkaz „ foreach “ , který automaticky prochází prvky kolekce od začátku do konce. C++11 zavádí podobnou funkci. Příkaz for usnadňuje iteraci sady prvků:
int moje_pole [ 5 ] = { 1 , 2 , 3 , 4 , 5 }; for ( int & x : my_array ) { x *= 2 ; }Tato forma for, anglicky nazývaná „range-based for“, navštíví každý prvek kolekce. To bude platit pro pole C , seznamy inicializátorů a jakékoli další typy , které mají funkce begin()a end()které vracejí iterátory . Všechny kontejnery ve standardní knihovně , které mají pár začátek/konec, budou fungovat s příkazem for v kolekci.
Takový cyklus bude fungovat také například s poli typu C, protože C++11 pro ně uměle zavádí potřebné pseudometody (začátek, konec a některé další).
// rozsahově založené procházení klasického pole int arr1 [] = { 1 , 2 , 3 }; for ( auto el : arr1 ); Lambda funkce a výrazyVe standardním C++, například při použití standardních algoritmů knihovny C++ sort and find , je často potřeba definovat predikátové funkce poblíž místa, kde je algoritmus volán. V jazyce pro to existuje pouze jeden mechanismus: schopnost definovat třídu funktoru (předávání instance třídy definované uvnitř funkce algoritmům je zakázáno (Meyers, Effective STL)). Tato metoda je často příliš nadbytečná a podrobná a pouze znesnadňuje čtení kódu. Standardní pravidla C++ pro třídy definované ve funkcích navíc neumožňují jejich použití v šablonách a znemožňují tak jejich použití.
Zřejmým řešením problému bylo umožnit definici výrazů lambda a funkcí lambda v C++11. Funkce lambda je definována takto:
[]( int x , int y ) { return x + y ; }Návratový typ této nepojmenované funkce se vypočítá jako decltype(x+y) . Návratový typ lze vynechat pouze v případě, že funkce lambda má tvar . To omezuje velikost funkce lambda na jeden výraz. return expression
Návratový typ lze zadat explicitně, například:
[]( int x , int y ) -> int { int z = x + y ; návrat z ; }Tento příklad vytvoří dočasnou proměnnou z pro uložení mezilehlé hodnoty. Stejně jako u normálních funkcí není tato mezihodnota mezi voláními zachována.
Návratový typ lze zcela vynechat, pokud funkce nevrací hodnotu (tj. návratový typ je void )
Je také možné použít odkazy na proměnné definované ve stejném rozsahu jako funkce lambda. Soubor takových proměnných se obvykle nazývá uzávěr . Uzávěry jsou definovány a používány následovně:
std :: vector < int > someList ; int celkem = 0 ; std :: for_each ( someList . begin (), someList . end (), [ & total ] ( int x ) { celkem += x ; }); std :: cout << celkem ;Tím se zobrazí součet všech prvků v seznamu. Celková proměnná je uložena jako součást uzavření funkce lambda. Protože odkazuje na proměnnou zásobníku total , může změnit její hodnotu.
Uzavírací proměnné pro lokální proměnné lze také definovat bez použití referenčního symbolu & , což znamená, že funkce zkopíruje hodnotu. To nutí uživatele deklarovat záměr odkazovat nebo kopírovat místní proměnnou.
Pro funkce lambda, u kterých je zaručeno, že se spouštějí ve svém rozsahu, je možné použít všechny proměnné zásobníku, aniž by bylo nutné na ně explicitně odkazovat:
std :: vector < int > someList ; int celkem = 0 ; std :: for_each ( someList . begin (), someList . end (), [ & ] ( int x ) { celkem += x ; });Metody implementace se mohou interně lišit, ale očekává se, že funkce lambda bude ukládat ukazatel na zásobník funkce, ve kterém byla vytvořena, spíše než pracovat s odkazy na jednotlivé proměnné zásobníku.
[&]Pokud je místo toho [=]použito , budou zkopírovány všechny použité proměnné, což umožní použití funkce lambda mimo rozsah původních proměnných.
Výchozí způsob převodu lze také doplnit o seznam jednotlivých proměnných. Pokud například potřebujete předávat většinu proměnných odkazem a jednu hodnotu, můžete použít následující konstrukci:
int celkem = 0 ; int hodnota = 5 ; [ & , hodnota ]( int x ) { total += ( x * hodnota ); } ( 1 ); //(1) volání funkce lambda s hodnotou 1To způsobí, že součet bude předán odkazem a hodnota hodnotou.
Pokud je funkce lambda definována v metodě třídy, je považována za přítele této třídy. Takové funkce lambda mohou používat odkaz na objekt typu třídy a přistupovat k jeho vnitřním polím:
[]( SomeType * typePtr ) { typePtr -> SomePrivateMemberFunction (); }To bude fungovat pouze v případě, že rozsah funkce lambda je metoda třídy SomeType .
Speciálním způsobem je implementována práce s ukazatelem this na objekt, se kterým aktuální metoda interaguje. Musí být výslovně označen ve funkci lambda:
[ this ]() { this -> SomePrivateMemberFunction (); }Pomocí formuláře [&]nebo [=]funkce lambda to zpřístupníte automaticky.
Typ funkcí lambda je závislý na implementaci; název tohoto typu je dostupný pouze kompilátoru. Pokud potřebujete předat funkci lambda jako parametr, musí to být typ šablony nebo uložený pomocí funkce std::function . Klíčové slovo auto vám umožňuje uložit funkci lambda lokálně:
auto myLambdaFunc = [ this ]() { this -> SomePrivateMemberFunction (); };Kromě toho, pokud funkce nebere žádné argumenty, ()můžete vynechat:
auto myLambdaFunc = []{ std :: cout << "ahoj" << std :: endl ; }; Syntaxe alternativní funkceNěkdy je potřeba implementovat šablonu funkce, která by vedla k výrazu, který má stejný typ a stejnou kategorii hodnot jako nějaký jiný výraz.
šablona < typename LHS , typename RHS > RETURN_TYPE AddingFunc ( const LHS & lhs , const RHS & rhs ) // jaký by měl být RETURN_TYPE? { return lhs + rhs ; }Aby měl výraz AddingFunc(x, y) stejný typ a stejnou kategorii hodnoty jako výraz lhs + rhs , když jsou zadány argumenty x a y , lze v C++11 použít následující definici:
template < typename LHS , typename RHS > decltype ( std :: declval < const LHS &> () + std :: declval < const RHS &> ()) AddingFunc ( const LHS & lhs , const RHS & rhs ) { return lhs + rhs ; }Tento zápis je poněkud těžkopádný a bylo by hezké mít možnost používat lhs a rhs místo std::declval<const LHS &>() a std::declval<const RHS &>(). Nicméně v další verzi
template < typename LHS , typename RHS > decltype ( lhs + rhs ) AddingFunc ( const LHS & lhs , const RHS & rhs ) // Neplatí v C++11 { return lhs + rhs ; }identifikátory lhs a rhs použité v operandu decltype, které jsou lépe čitelné pro člověka, nemohou označovat volby deklarované později. K vyřešení tohoto problému C++11 zavádí novou syntaxi pro deklarování funkcí s návratovým typem na konci:
template < typename LHS , typename RHS > auto AddingFunc ( const LHS & lhs , const RHS & rhs ) -> decltype ( lhs + rhs ) { return lhs + rhs ; }Je však třeba poznamenat, že v obecnější implementaci AddingFunc níže nová syntaxe netěží ze stručnosti:
šablona < typename LHS , typename RHS > auto AddingFunc ( LHS && lhs , RHS && rhs ) -> decltype ( std :: vpřed < LHS > ( lhs ) + std :: vpřed < RHS > ( rhs )) { return std :: vpřed < LHS > ( lhs ) + std :: vpřed < RHS > ( rhs ); } šablona < typename LHS , typename RHS > auto AddingFunc ( LHS && lhs , RHS && rhs ) -> decltype ( std :: declval < LHS > () + std :: declval < RHS > ()) // stejný efekt jako u std::forward výše { return std :: vpřed < LHS > ( lhs ) + std :: vpřed < RHS > ( rhs ); } template < typename LHS , typename RHS > decltype ( std :: declval < LHS > () + std :: declval < RHS > ()) // stejný efekt jako uvedení typu na konec AddingFunc ( LHS && lhs , RHS && rhs ) { return std :: vpřed < LHS > ( lhs ) + std :: vpřed < RHS > ( rhs ); }Novou syntaxi lze použít v jednodušších deklaracích a deklaracích:
struct SomeStruct { auto FuncName ( int x , int y ) -> int ; }; auto SomeStruct :: FuncName ( int x , int y ) -> int { vrátit x + y _ }Použití klíčového slova " " autov tomto případě znamená pouze pozdní označení návratového typu a nesouvisí s jeho automatickým vyvozováním.
Vylepšení konstruktorů objektůStandardní C++ neumožňuje, aby byl jeden konstruktor třídy volán z jiného konstruktoru stejné třídy; každý konstruktor musí plně inicializovat všechny členy třídy nebo k tomu volat metody třídy. Nekonstantní členy třídy nelze inicializovat v místě, kde jsou tyto členy deklarovány.
C++11 se těchto problémů zbaví.
Nový standard umožňuje volat jeden konstruktor třídy z jiného (tzv. delegování). To vám umožňuje psát konstruktory, které používají chování jiných konstruktorů, aniž byste zaváděli duplicitní kód.
Příklad:
class SomeType { int číslo ; veřejnost : SomeType ( int new_number ) : number ( new_number ) {} SomeType () : SomeType ( 42 ) {} };Z příkladu můžete vidět, že konstruktor SomeTypebez argumentů volá konstruktor stejné třídy s celočíselným argumentem pro inicializaci proměnné number. Podobného efektu lze dosáhnout zadáním počáteční hodnoty 42 pro tuto proměnnou hned při její deklaraci.
class SomeType { int číslo = 42 ; veřejnost : SomeType () {} explicitní SomeType ( int new_number ) : number ( new_number ) {} };Jakýkoli konstruktor třídy se inicializuje numberna 42, pokud mu sám nepřiřadí jinou hodnotu.
Java , C# a D jsou příklady jazyků, které také řeší tyto problémy .
Je třeba poznamenat, že pokud je v C++03 objekt považován za plně vytvořený, když jeho konstruktor dokončí provádění, pak v C++11 po provedení alespoň jednoho delegujícího konstruktoru budou ostatní konstruktory pracovat na plně stavěný objekt. Navzdory tomu budou objekty odvozené třídy vytvořeny až poté, co budou provedeny všechny konstruktory základních tříd.
Explicitní náhrada virtuálních funkcí a konečnostJe možné, že signatura virtuální metody byla změněna v základní třídě nebo byla původně nesprávně nastavena v odvozené třídě. V takových případech daná metoda v odvozené třídě nepřepíše odpovídající metodu v základní třídě. Pokud tedy programátor správně nezmění signaturu metody ve všech odvozených třídách, metoda nemusí být během provádění programu správně volána. Například:
struct Base { virtual void some_func (); }; struct Odvozeno : Base { void sone_func (); };Zde je název virtuální funkce deklarovaný v odvozené třídě napsán chybně, takže taková funkce nepřepíše Base::some_func, a proto nebude volána polymorfně přes ukazatel nebo odkaz na základní podobjekt.
C++ 11 přidá možnost sledovat tyto problémy v době kompilace (spíše než v době běhu). Kvůli zpětné kompatibilitě je tato funkce volitelná. Nová syntaxe je uvedena níže:
struktura B { virtual void some_func (); virtuální void f ( int ); virtuální void g () const ; }; struktura D1 : veřejný B { void sone_func () override ; // chyba: neplatný název funkce void f ( int ) override ; // OK: přepíše stejnou funkci v základní třídě virtual void f ( long ) override ; // chyba: neshoda typu parametru virtual void f ( int ) const override ; // chyba: neshoda funkce cv-kvalifikace virtual int f ( int ) override ; // chyba: neshoda návratového typu virtual void g () const final ; // OK: přepíše stejnou funkci v základní třídě virtual void g ( long ); // OK: nová virtuální funkce }; struktura D2 : D1 { virtuální void g () const ; // chyba: pokus o nahrazení finální funkce };Přítomnost specifikátoru pro virtuální funkci finalznamená, že její další nahrazení je nemožné. Třída definovaná s konečným specifikátorem také nemůže být použita jako základní třída:
struktura F konečná { int x , y ; }; struct D : F // chyba: dědění z finálních tříd není povoleno { int z ; };Identifikátory overridea finalmají zvláštní význam pouze při použití v určitých situacích. V jiných případech je lze použít jako běžné identifikátory (například jako název proměnné nebo funkce).
Nulová konstanta ukazateleOd příchodu C v roce 1972 hraje konstanta 0 dvojí roli celého čísla a nulového ukazatele. Jedním ze způsobů, jak se vypořádat s touto nejednoznačností, která je vlastní jazyku C, je makro NULL, které obvykle provádí substituci ((void*)0)nebo 0. C++ se v tomto ohledu od C liší a umožňuje pouze použití 0nulového ukazatele jako konstanty. To vede ke špatné interakci s přetížením funkcí:
void foo ( char * ); void foo ( int );Pokud je makro NULLdefinováno jako (což je běžné v C++), výsledkem 0řádku bude volání , ne jak by rychlý pohled na kód mohl naznačovat, což téměř jistě není to, co programátor zamýšlel. foo(NULL);foo(int)foo(char *)
Jednou z novinek C++11 je nové klíčové slovo pro popis konstanty nulového ukazatele - nullptr. Tato konstanta je typu std::nullptr_t, kterou lze implicitně převést na typ libovolného ukazatele a porovnat s jakýmkoli ukazatelem. Implicitní převod na integrální typ není povolen, s výjimkou bool. Původní návrh standardu neumožňoval implicitní konverzi na boolean, ale skupina pro tvorbu standardů takové konverze umožňovala kvůli kompatibilitě s konvenčními typy ukazatelů. Navrhované znění bylo změněno po jednomyslném hlasování v červnu 2008 [1] .
Pro zpětnou kompatibilitu lze konstantu 0použít také jako nulový ukazatel.
char * pc = nullptr ; // true int * pi = nullptr ; // true bool b = nullptr ; // že jo. b=nepravda. int i = nullptr ; // chyba foo ( nullptr ); // volá foo(char *), ne foo(int);Konstrukce, kde je zaručeno, že je ukazatel prázdný, jsou často jednodušší a bezpečnější než ostatní – takže je můžete přetížit . nullptr_t
třída Užitečné zatížení ; třída SmartPtr { SmartPtr () = výchozí ; SmartPtr ( nullptr_t ) {} // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< explicitní SmartPtr ( Payload * aData ) : fData ( aData ) {} // kopírování konstruktorů a op= vynechání ~ SmartPtr () { delete fData ; } soukromý : Užitečná zátěž * fData = nullptr ; } SmartPtr getPayload1 () { return nullptr ; } // Bude zavoláno přetížení SmartPtr(nullptr_t). Silně typované výčtyVe standardním C++ nejsou výčty typově bezpečné. Ve skutečnosti jsou reprezentovány celými čísly, a to navzdory skutečnosti, že samotné typy výčtů se od sebe liší. To umožňuje provádět srovnání mezi dvěma hodnotami z různých výčtů. Jedinou možností, kterou C++03 nabízí k ochraně výčtů, je implicitně nepřevádět celá čísla nebo prvky jednoho výčtu na prvky jiného výčtu. Také způsob, jakým je reprezentován v paměti (typ celé číslo), je závislý na implementaci, a proto není přenosný. A konečně, prvky výčtu mají společný rozsah, což znemožňuje vytvářet prvky se stejným názvem v různých výčtech.
C++11 nabízí speciální klasifikaci těchto výčtů, bez výše uvedených nevýhod. K popisu takových výčtů se používá deklarace enum class(může být také použita enum structjako synonymum):
enum class enumeration { Val1 , Val2 , Val3 = 100 , Val4 , /* = 101 */ };Takový výčet je typově bezpečný. Prvky výčtu třídy nelze implicitně převést na celá čísla. V důsledku toho je také nemožné srovnání s celými čísly (výraz Enumeration::Val4 == 101má za následek chybu kompilace).
Typ výčtu třídy je nyní nezávislý na implementaci. Ve výchozím nastavení, stejně jako v případě výše, je tento typ int, ale v ostatních případech lze typ nastavit ručně takto:
enum class Enum2 : unsigned int { Val1 , Val2 };Rozsah členů výčtu je určen rozsahem názvu výčtu. Použití názvů prvků vyžaduje zadání názvu výčtu třídy. Takže například hodnota Enum2::Val1je definována, ale hodnota Val1 není definována.
C++11 navíc nabízí možnost explicitně určit rozsah a základní typy pro běžné výčty:
enum Enum3 : unsigned long { Val1 = 1 , Val2 };V tomto příkladu jsou názvy prvků výčtu definovány v prostoru výčtu (Enum3::Val1), ale kvůli zpětné kompatibilitě jsou názvy prvků dostupné také ve společném oboru.
Také v C++11 je možné předem deklarovat výčty. V předchozích verzích C++ to nebylo možné, protože velikost výčtu závisela na jeho prvcích. Takové deklarace lze použít pouze v případě, že je specifikována velikost výčtu (explicitně nebo implicitně):
enum Enum1 ; // neplatné pro C++ a C++11; základní typ nelze určit enum Enum2 : unsigned int ; // true pro C++11, základní typ explicitně specifikovaný enum class Enum3 ; // true pro C++11, základní typ je int enum class Enum4 : unsigned int ; // platí pro C++11. enum Enum2 : unsigned short ; // neplatný pro C++11, protože Enum2 byl dříve deklarován s jiným základním typem Lomené závorkyStandardní analyzátory C++ vždy definují kombinaci znaků ">>" jako operátor posunu vpravo. Absence mezery mezi uzavíracími lomenými závorkami v parametrech šablony (pokud jsou vnořené) je považována za chybu syntaxe.
C++11 v tomto případě vylepšuje chování analyzátoru tak, že více pravoúhlých závorek bude interpretováno jako uzavírací seznamy argumentů šablony.
Popsané chování lze opravit ve prospěch starého přístupu pomocí závorek.
šablona < class T > class Y { /* ... */ }; Y < X < 1 >> x3 ; // Správně, stejně jako "Y<X<1> > x3;". Y < X < 6 >> 1 >> x4 ; // Chyba syntaxe. Musíte napsat "Y<X<(6>>1)>> x4;".Jak je uvedeno výše, tato změna není zcela kompatibilní s předchozím standardem.
Explicitní převodní operátoryStandard C++ poskytuje klíčové slovo explicitjako modifikátor pro jednoparametrové konstruktory, takže takové konstruktory nefungují jako implicitní konverzní konstruktory. To však nijak neovlivňuje skutečné operátory konverze. Třída inteligentního ukazatele může například obsahovat operator bool()napodobování běžného ukazatele. Takový operátor lze volat například takto: if(smart_ptr_variable)(větvení se provede, pokud ukazatel není null). Problém je, že takový operátor nechrání před dalšími neočekávanými konverzemi. Vzhledem k tomu, že typ boolje deklarován jako aritmetický typ v C++, je možný implicitní převod na jakýkoli typ celého čísla nebo dokonce na typ s plovoucí desetinnou čárkou, což může vést k neočekávaným matematickým operacím.
V C++11 se klíčové slovo explicitvztahuje také na konverzní operátory. Stejně jako konstruktory chrání před neočekávanými implicitními konverzemi. Nicméně situace, kdy jazyk kontextově očekává booleovský typ (například v podmíněných výrazech, cyklech a operandech logických operátorů), jsou považovány za explicitní převody a operátor explicitního boolovského převodu je vyvolán přímo.