C++11 [1] [2] eller ISO/IEC 14882:2011 [3] (i arbetet med standarden hade den kodnamnet C++0x [4] [5] ) — en ny version av språkstandarden C++ istället för den tidigare giltiga ISO /IEC 14882:2003. Den nya standarden innehåller tillägg till kärnan i språket och en utökning av standardbiblioteket, inklusive det mesta av TR1 - utom kanske biblioteket med speciella matematiska funktioner. Nya versioner av standarderna, tillsammans med några andra C++-standardiseringsdokument, publiceras på ISO C++-kommitténs webbplats [6] . C++ programmeringsexempel
Programmeringsspråk genomgår en gradvis utveckling av sina möjligheter (för tillfället, efter C++11, har följande standardtillägg publicerats: C++14, C++17, C++20). Denna process orsakar oundvikligen kompatibilitetsproblem med befintlig kod. Appendix C.2 [diff.cpp03] i Final Draft International Standard N3290 beskriver några av inkompatibiliteterna mellan C++11 och C++03.
Som redan nämnts kommer ändringarna att påverka både C++-kärnan och dess standardbibliotek.
Vid utvecklingen av varje avsnitt av den framtida standarden använde kommittén ett antal regler:
Uppmärksamhet ägnas åt nybörjare, som alltid kommer att utgöra majoriteten av programmerare. Många nybörjare försöker inte fördjupa sina kunskaper om C++, utan begränsar sig till att använda det när de arbetar med snäva specifika uppgifter [7] . Dessutom, med tanke på mångsidigheten hos C++ och dess användningsbredd (inklusive både mångfalden av applikationer och programmeringsstilar), kan även proffs hitta sig själva som nya i nya programmeringsparadigm .
Kommitténs primära uppgift är att utveckla kärnan i C++-språket. Kärnan har förbättrats avsevärt, stöd för flera trådar har lagts till, stöd för generisk programmering har förbättrats , initiering har förenats och arbete har gjorts för att förbättra dess prestanda.
För enkelhetens skull är kärnans funktioner och ändringar uppdelade i tre huvuddelar: prestandaförbättringar, bekvämlighetsförbättringar och ny funktionalitet. Individuella element kan tillhöra flera grupper, men kommer endast att beskrivas i en - den mest lämpliga.
Dessa språkkomponenter introduceras för att minska minneskostnader eller förbättra prestanda.
Tillfälliga objektreferenser och flytta semantikEnligt C++-standarden kan ett temporärt objekt som är ett resultat av utvärderingen av ett uttryck skickas till funktioner, men endast genom en konstant referens ( const & ). Funktionen kan inte avgöra om det skickade objektet kan anses vara temporärt och modifierbart (ett const-objekt som också kan skickas av en sådan referens kan inte modifieras (lagligt)). Detta är inte ett problem för enkla strukturer som complex, men för komplexa typer som kräver minnesallokering-deallokering kan det vara tidskrävande att förstöra ett temporärt objekt och skapa ett permanent, medan man helt enkelt kan skicka pekare direkt.
C++11 introducerar en ny typ av referens , rvalue - referensen . Dess deklaration är: typ && . Nya regler för överbelastningsupplösning tillåter dig att använda olika överbelastade funktioner för icke-konst temporära objekt, betecknade med rvärden, och för alla andra objekt. Denna innovation möjliggör implementering av den så kallade rörelsesemantiken .
Till exempel std::vector är ett enkelt omslag runt en C-array och en variabel som lagrar dess storlek. Kopieringskonstruktören std::vector::vector(const vector &x)kommer att skapa en ny array och kopiera informationen; överföringskonstruktorn std::vector::vector(vector &&x)kan helt enkelt utbyta pekare och variabler som innehåller längden.
Annonsexempel.
mall < klass T > klassvektor _ { vektor ( konst vektor & ); // Kopiera konstruktor (långsam) vektor ( vektor && ); // Överför konstruktor från ett temporärt objekt (snabb) vektor & operator = ( const vektor & ); // Regelbunden tilldelning (långsam) vektor & operator = ( vektor && ); // Flytta tillfälligt objekt (snabbt) void foo () & ; // Funktion som bara fungerar på ett namngivet objekt (långsamt) void foo () && ; // Funktion som bara fungerar för ett tillfälligt objekt (snabbt) };Det finns flera mönster förknippade med tillfälliga länkar, varav de två viktigaste är och . Den första gör ett vanligt namngivet objekt till en tillfällig referens: moveforward
// std::move mall exempel void bar ( std :: string && x ) { statisk std :: stringsomeString ; _ someString = std :: flytta ( x ); // inuti funktionen x=string&, därav det andra draget för att anropa dragtilldelningen } std :: trådig ; _ bar ( std :: flytta ( y )); // första drag förvandlar sträng& till sträng&& till anropsfältetMallen används endast i metaprogrammering, kräver en explicit mallparameter (den har två oskiljbara överbelastningar) och är associerad med två nya C++-mekanismer. Den första är länklimning: , sedan . För det andra kräver funktionen bar() ovan ett temporärt objekt på utsidan, men på insidan är x-parametern ett ordinärt namn (lvalue) för reserv, vilket gör det omöjligt att automatiskt skilja parametern string& från parametern string&&. I en vanlig icke-mallfunktion kan programmeraren sätta move(), men hur är det med mallen? forwardusing One=int&&; using Two=One&;Two=int&
// exempel på att använda mallen std::forward class Obj { std :: stringfield ; _ mall < classT > _ Obj ( T && x ) : fält ( std :: framåt < T > ( x )) {} };Denna konstruktor täcker de vanliga (T=sträng&), kopierings- (T=const sträng&) och flytta (T=sträng) överbelastningar med referenslimning. Och framåt gör ingenting eller expanderar till std::move beroende på typen av T, och konstruktorn kommer att kopiera om det är en kopia och flytta om det är ett drag.
Generiska konstantuttryckC++ har alltid haft konceptet med konstanta uttryck. Således gav uttryck som 3+4 alltid samma resultat utan att orsaka några biverkningar. I sig själva ger konstanta uttryck ett bekvämt sätt för C++-kompilatorer att optimera resultatet av kompileringen. Kompilatorer utvärderar resultaten av sådana uttryck endast vid kompileringstid och lagrar de redan beräknade resultaten i programmet. Således utvärderas sådana uttryck endast en gång. Det finns också ett fåtal fall där språkstandarden kräver användning av konstanta uttryck. Sådana fall kan till exempel vara definitioner av externa arrayer eller enumvärden.
Ovanstående kod är olaglig i C++ eftersom GiveFive() + 7 inte tekniskt sett är ett konstant uttryck känt vid kompileringstillfället. Kompilatorn vet helt enkelt inte vid den tidpunkten att funktionen faktiskt returnerar en konstant vid körning. Anledningen till detta kompilatorresonemang är att den här funktionen kan påverka tillståndet för en global variabel, anropa en annan icke-konst körtidsfunktion och så vidare.
C++11 introducerar nyckelordet constexpr , som låter användaren säkerställa att antingen en funktion eller en objektkonstruktor returnerar en kompileringstidskonstant. Koden ovan kan skrivas om så här:
constexpr int GiveFive () { return 5 ;} int some_value [ GiveFive () + 7 ]; // skapa en array med 12 heltal; tillåtet i C++11Detta nyckelord låter kompilatorn förstå och verifiera att GiveFive returnerar en konstant.
Användningen av constexpr sätter mycket strikta begränsningar för funktionernas åtgärder:
I den tidigare versionen av standarden kunde endast heltals- eller enumtypvariabler användas i konstanta uttryck. I C++11 upphävs denna begränsning för variabler vars definition föregås av nyckelordet constexpr:
constexpr dubbel accelerationOfGravity = 9,8 ; constexpr dubbelmåneGravity = accelerationOfGravity / 6 ; _Sådana variabler anses redan implicit betecknas av nyckelordet const . De kan endast innehålla resultaten av konstanta uttryck eller konstruktörerna av sådana uttryck.
Om det är nödvändigt att konstruera konstanta värden från användardefinierade typer, kan konstruktörer av sådana typer också deklareras med constexpr . En konstantuttryckskonstruktor, liksom konstantfunktioner, måste också definieras innan den används för första gången i den aktuella kompileringsenheten. En sådan konstruktör måste ha en tom kropp, och en sådan konstruktor måste initiera medlemmarna av sin typ med endast konstanter.
Ändringar i definitionen av enkla dataI standard C++ kan endast strukturer som uppfyller en viss uppsättning regler betraktas som en vanlig datatyp ( POD). Det finns goda skäl att förvänta sig att dessa regler utökas så att fler typer betraktas som POD:er. Typer som uppfyller dessa regler kan användas i en C-kompatibel objektlagerimplementering, men C++03s lista över dessa regler är alltför restriktiv.
C++11 kommer att lätta på flera regler angående definitionen av enkla datatyper.
En klass anses vara en enkel datatyp om den är trivial , har en standardlayout ( standardlayout ) och om typerna av alla dess icke-statiska datamedlemmar också är enkla datatyper.
En trivial klass är en klass som:
En klass med standardplacering är en klass som:
I standard C++ måste kompilatorn instansiera en mall när den stöter på sin fulla specialisering i en översättningsenhet. Detta kan avsevärt öka kompileringstiden, särskilt när mallen instansieras med samma parametrar i ett stort antal översättningsenheter. Det finns för närvarande inget sätt att berätta för C++ att det inte ska finnas någon instansiering.
C++11 introducerade idén med externa mallar. C++ har redan en syntax för att tala om för kompilatorn att en mall ska instansieras vid en viss punkt:
mallklass std :: vektor < MyClass > ; _C++ saknar förmågan att förhindra kompilatorn från att instansiera en mall i en översättningsenhet. C++11 utökar helt enkelt denna syntax:
extern mallklass std :: vektor < MyClass > ; _Detta uttryck säger åt kompilatorn att inte instansiera mallen i denna översättningsenhet.
Dessa funktioner är avsedda att göra språket lättare att använda. De låter dig stärka typsäkerheten, minimera kodduplicering, göra det svårare för kod att missbrukas och så vidare.
InitialiseringslistorKonceptet med initialiseringslistor kom till C++ från C. Tanken är att en struktur eller array kan skapas genom att skicka en lista med argument i samma ordning som strukturens medlemmar definieras. Initialiseringslistor är rekursiva, vilket gör att de kan användas för arrayer av strukturer och strukturer som innehåller kapslade strukturer.
struct objekt { flyta först ; int andra ; }; Objekt skalär = { 0.43f , 10 }; // ett objekt, med first=0.43f och second=10 Object anArray [] = {{ 13.4f , 3 }, { 43.28f , 29 }, { 5.934f , 17 }}; // array av tre objektInitialiseringslistor är mycket användbara för statiska listor och när du vill initiera en struktur till ett specifikt värde. C++ innehåller också konstruktorer, som kan innehålla det allmänna arbetet med att initiera objekt. C++-standarden tillåter användning av initialiseringslistor för strukturer och klasser, förutsatt att de överensstämmer med POD-definitionen (Plain Old Data). Icke-POD-klasser kan inte använda initialiseringslistor för initiering, inklusive standard C++-behållare som vektorer.
C++11 har associerat konceptet med initialiseringslistor och en mallklass som heter std::initializer_list . Detta gjorde det möjligt för konstruktörer och andra funktioner att ta emot initialiseringslistor som parametrar. Till exempel:
klass SequenceClass { offentliga : SequenceClass ( std :: initializer_list < int > list ); };Den här beskrivningen låter dig skapa en SequenceClass från en sekvens av heltal enligt följande:
SequenceClass someVar = { 1 , 4 , 5 , 6 };Detta visar hur en speciell typ av konstruktör fungerar för en initialiseringslista. Klasser som innehåller sådana konstruktorer behandlas på ett speciellt sätt under initiering (se nedan ).
Klassen std::initializer_list<> är definierad i standardbiblioteket C++11. Objekt av den här klassen kan dock endast skapas statiskt av C++11-kompilatorn med hjälp av syntaxen {}. Listan kan kopieras efter att den skapats, men detta kommer att kopieras för referens. Initieringslistan är const: varken dess medlemmar eller deras data kan ändras efter skapandet.
Eftersom std::initializer_list<> är en fullfjädrad typ, kan den användas i mer än bara konstruktörer. Vanliga funktioner kan ta inskrivna initialiseringslistor som ett argument, till exempel:
void Funktionsnamn ( std :: initializer_list < float > list ); Funktionsnamn ({ 1.0f , -3.45f , -0.4f });Standardbehållare kan initieras så här:
std :: vektor < std :: sträng > v = { "xyzzy" , "plugh" , "abracadabra" }; std :: vektor < std :: sträng > v { "xyzzy" , "plugh" , "abracadabra" }; Generisk initieringC++-standarden innehåller ett antal problem relaterade till typinitiering. Det finns flera sätt att initiera typer, och alla leder inte till samma resultat. Till exempel kan den traditionella syntaxen för en initierande konstruktor se ut som en funktionsdeklaration, och extra försiktighet måste iakttas för att förhindra att kompilatorn tolkar den fel. Endast aggregattyper och POD-typer kan initieras med aggregatinitierare (av typen SomeType var = {/*stuff*/};).
C++11 tillhandahåller en syntax som gör att en enda form av initiering kan användas för alla typer av objekt genom att utöka initieringslistans syntax:
struct BasicStruct { int x ; dubbelt y ; }; struct AltStruct { AltStruct ( int x , dubbel y ) : x_ ( x ), y_ ( y ) {} privat : int x_ ; dubbelt y_ ; }; BasicStruct var1 { 5 , 3.2 }; AltStruct var2 { 2 , 4.3 };Att initiera var1 fungerar precis som att initiera aggregat, det vill säga att varje objekt initieras genom att kopiera motsvarande värde från initieringslistan. Vid behov kommer implicit typkonvertering att tillämpas. Om den önskade transformationen inte existerar kommer källkoden att betraktas som ogiltig. Under initieringen av var2 kommer konstruktorn att anropas.
Det är möjligt att skriva kod så här:
struktur IdString { std :: strängnamn ; _ int identifierare ; }; IdString GetString () { returnera { "SomeName" , 4 }; // Observera bristen på explicita typer }Generisk initiering ersätter inte helt syntax för konstruktorinitiering. Om en klass har en konstruktor som tar en initialiseringslista ( TypeName(initializer_list<SomeType>); ) som argument, kommer den att ha företräde framför andra alternativ för att skapa objekt. Till exempel, i C++11 innehåller std::vector en konstruktor som tar en initialiseringslista som ett argument:
std :: vektor < int > theVec { 4 };Den här koden kommer att resultera i ett konstruktoranrop som tar en initialiseringslista som ett argument, snarare än en enparameters konstruktor som skapar en behållare av den givna storleken. För att anropa denna konstruktor måste användaren använda standardsyntaxen för konstruktoranrop.
Skriv inferensI standard C++ (och C) måste typen av en variabel anges uttryckligen. Men med tillkomsten av malltyper och mallmetaprogrammeringstekniker kan typen av vissa värden, särskilt funktionsreturvärden, inte enkelt specificeras. Detta leder till svårigheter att lagra mellanliggande data i variabler, ibland kan det vara nödvändigt att känna till den interna strukturen för ett visst metaprogrammeringsbibliotek.
C++11 erbjuder två sätt att lindra dessa problem. För det första kan definitionen av en explicit initierbar variabel innehålla nyckelordet auto . Detta kommer att resultera i skapandet av en variabel av typen av initialiseringsvärdet:
auto someStrangeCallableType = std :: bind ( & SomeFunction , _2 , _1 , someObject ); auto annanVariabel = 5 ;Typen someStrangeCallableType blir den typ som den konkreta implementeringen av mallfunktionen returnerar std::bindför de givna argumenten. Denna typ kommer lätt att bestämmas av kompilatorn under semantisk analys, men programmeraren måste göra lite forskning för att bestämma typen.
Den andraVariabeltypen är också väldefinierad, men kan lika gärna definieras av programmeraren. Denna typ är int , samma som en heltalskonstant.
Dessutom kan nyckelordet decltype användas för att bestämma typen av ett uttryck vid kompileringstillfället . Till exempel:
int someInt ; decltype ( someInt ) otherIntegerVariable = 5 ;Att använda decltype är mest användbart i samband med auto , eftersom typen av en variabel som deklareras som auto endast är känd för kompilatorn. Att använda decltype kan också vara ganska användbart i uttryck som använder operatoröverbelastning och mallspecialisering.
autokan också användas för att minska kodredundans. Till exempel, istället för:
for ( vektor < int >:: const_iterator itr = myvec . cbegin (); itr != myvec . cend (); ++ itr )programmeraren kan skriva:
för ( auto itr = myvec . cbegin (); itr != myvec . cend (); ++ itr )Skillnaden blir särskilt märkbar när en programmerare använder ett stort antal olika behållare, även om det fortfarande finns ett bra sätt att minska redundant kod med typedef.
En typ som är markerad med decltype kan skilja sig från den typ som antas med auto .
#inkludera <vektor> int main () { const std :: vektor < int > v ( 1 ); auto a = v [ 0 ]; // typ a - int decltype ( v [ 0 ]) b = 1 ; // typ b - const int& (returvärde // std::vector<int>::operator[](size_type) const) auto c = 0 ; // skriv c - int auto d = c ; // typ d - int decltype ( c ) e ; // typ e - int, typ av enhet som heter c decltype (( c )) f = c ; // typ f är int& eftersom (c) är ett lvärde decltype ( 0 ) g ; // typ g är int eftersom 0 är ett rvärde } For-loop genom en samlingI standard C++ kräver det mycket kod att iterera över elementen i en samling . Vissa språk, som C# , har faciliteter som tillhandahåller en " foreach " -sats som automatiskt går igenom elementen i en samling från början till slut. C++11 introducerar en liknande anläggning. For -satsen gör det lättare att iterera över en samling element:
int my_array [ 5 ] = { 1 , 2 , 3 , 4 , 5 }; för ( int & x : my_array ) { x *= 2 ; }Denna form av för, som kallas "range-based for" på engelska, kommer att besöka varje del av samlingen. Detta kommer att gälla för C -matriser , initialiseringslistor och alla andra typer som har funktioner begin()och end()som returnerar iteratorer . Alla behållare i standardbiblioteket som har ett start/slut-par kommer att fungera med en for-sats på samlingen.
En sådan cykel kommer också att fungera, till exempel med C-liknande arrayer, eftersom C++11 introducerar på konstgjord väg de nödvändiga pseudometoderna för dem (början, slutet och några andra).
// områdesbaserad genomgång av den klassiska matrisen int arr1 [] = { 1 , 2 , 3 }; for ( auto el : arr1 ); Lambdafunktioner och uttryckI standard C++, till exempel, när man använder standard C++ biblioteksalgoritmer sortera och hitta , finns det ofta ett behov av att definiera predikatfunktioner nära där algoritmen anropas. Det finns bara en mekanism i språket för detta: möjligheten att definiera en funktionsklass (att skicka en instans av en klass definierad inuti en funktion till algoritmer är förbjuden (Meyers, Effektiv STL)). Ofta är denna metod för överflödig och utförlig och gör det bara svårt att läsa koden. Dessutom tillåter inte standard C++-reglerna för klasser definierade i funktioner att de används i mallar och gör dem därför omöjliga att använda.
Den uppenbara lösningen på problemet var att tillåta definitionen av lambda-uttryck och lambda-funktioner i C++11. Lambdafunktionen definieras så här:
[]( int x , int y ) { return x + y ; }Returtypen för denna namnlösa funktion beräknas som decltype(x+y) . Returtypen kan endast utelämnas om lambdafunktionen är av formen . Detta begränsar storleken på lambdafunktionen till ett enda uttryck. return expression
Returtypen kan anges explicit, till exempel:
[]( int x , int y ) -> int { int z = x + y ; returnera z ; }Detta exempel skapar en temporär variabel z för att lagra ett mellanvärde. Som med normala funktioner bevaras inte detta mellanvärde mellan samtalen.
Returtypen kan utelämnas helt om funktionen inte returnerar ett värde (det vill säga returtypen är ogiltig )
Det är också möjligt att använda referenser till variabler definierade i samma omfång som lambdafunktionen. En uppsättning av sådana variabler kallas vanligtvis en stängning . Förslutningar definieras och används enligt följande:
std :: vektor < int > someList ; int totalt = 0 ; std :: for_each ( someList . begin (), someList . end (), [ & total ]( int x ) { totalt += x ; }); std :: cout << totalt ;Detta kommer att visa summan av alla element i listan. Den totala variabeln lagras som en del av stängningen av lambdafunktionen. Eftersom den refererar till stackvariabeln total kan den ändra dess värde.
Stängningsvariabler för lokala variabler kan också definieras utan att använda referenssymbolen & , vilket betyder att funktionen kommer att kopiera värdet. Detta tvingar användaren att deklarera en avsikt att referera till eller kopiera en lokal variabel.
För lambda-funktioner som garanterat kommer att köras inom sin omfattning är det möjligt att använda alla stackvariabler utan behov av explicita referenser till dem:
std :: vektor < int > someList ; int totalt = 0 ; std :: for_each ( someList . begin (), someList . end (), [ & ]( int x ) { totalt += x ; });Implementeringsmetoderna kan variera internt, men lambda-funktionen förväntas lagra en pekare till stacken av funktionen den skapades i, snarare än att arbeta på individuella stackvariabelreferenser.
[&]Om används istället [=]kommer alla använda variabler att kopieras, vilket gör att lambda-funktionen kan användas utanför omfånget för de ursprungliga variablerna.
Standardöverföringsmetoden kan också kompletteras med en lista över individuella variabler. Om du till exempel behöver skicka de flesta variablerna genom referens, och en efter värde, kan du använda följande konstruktion:
int totalt = 0 ; int värde = 5 ; [ & , värde ]( int x ) { totalt += ( x * värde ); } ( 1 ); //(1) anropa lambdafunktion med värde 1Detta kommer att göra att totalen skickas genom referens och värde efter värde.
Om en lambda-funktion är definierad i en klassmetod anses den vara en vän till den klassen. Sådana lambda-funktioner kan använda en referens till ett objekt av klasstypen och komma åt dess interna fält:
[]( SomeType * typePtr ) { typePtr -> SomePrivateMemberFunction (); }Detta fungerar bara om omfattningen av lambda-funktionen är en klassmetod SomeType .
Arbetet med denna pekare till objektet som den aktuella metoden interagerar med implementeras på ett speciellt sätt. Det måste uttryckligen markeras i lambdafunktionen:
[ this ]() { this -> SomePrivateMemberFunction (); }Genom att använda ett formulär [&]eller [=]en lambdafunktion blir detta tillgängligt automatiskt.
Typen av lambdafunktioner är implementeringsberoende; namnet på denna typ är endast tillgängligt för kompilatorn. Om du behöver skicka en lambdafunktion som parameter måste den vara en malltyp, eller lagras med std::function . Nyckelordet auto låter dig spara en lambdafunktion lokalt:
auto myLambdaFunc = [ this ]() { this -> SomePrivateMemberFunction (); };Dessutom, om funktionen inte tar några argument, kan ()du utelämna:
auto myLambdaFunc = []{ std :: cout << "hej" << std :: endl ; }; Alternativ funktionssyntaxIbland finns det ett behov av att implementera en funktionsmall som skulle resultera i ett uttryck som har samma typ och samma värdekategori som något annat uttryck.
mall < typnamn LHS , typnamn RHS > RETURN_TYPE AddingFunc ( const LHS & lhs , const RHS & rhs ) // vad ska RETURN_TYPE vara? { returnera lhs + rhs ; }För att uttrycket AddingFunc(x, y) ska ha samma typ och samma värdekategori som uttrycket lhs + rhs när de ges argumenten x och y , kan följande definition användas inom C++11:
mall < typnamn LHS , typnamn RHS > decltype ( std :: declval < const LHS &> () + std :: declval < const RHS &> ()) AddingFunc ( const LHS & lhs , const RHS & rhs ) { returnera lhs + rhs ; }Denna notation är något besvärlig, och det skulle vara trevligt att kunna använda lhs och rhs istället för std::declval<const LHS &>() respektive std::declval<const RHS &>(). Dock i nästa version
mall < typnamn LHS , typnamn RHS > decltype ( lhs + rhs ) AddingFunc ( const LHS & lhs , const RHS & rhs ) // Ej giltigt i C++11 { returnera lhs + rhs ; }mer läsbara för människor, lhs- och rhs-identifierarna som används i decltype -operanden kan inte beteckna alternativ som deklareras senare. För att lösa detta problem introducerar C++11 en ny syntax för att deklarera funktioner med en returtyp i slutet:
mall < typnamn LHS , typnamn RHS > auto AddingFunc ( const LHS & lhs , const RHS & rhs ) -> decltype ( lhs + rhs ) { returnera lhs + rhs ; }Det bör dock noteras att i den mer generiska AddingFunc-implementeringen nedan drar den nya syntaxen inte fördel av korthet:
mall < typnamn LHS , typnamn RHS > auto AddingFunc ( LHS && lhs , RHS && rhs ) -> decltype ( std :: framåt < LHS > ( lhs ) + std :: framåt < RHS > ( rhs )) { return std :: framåt < LHS > ( lhs ) + std :: framåt < RHS > ( rhs ); } mall < typnamn LHS , typnamn RHS > auto AddingFunc ( LHS && lhs , RHS && rhs ) -> decltype ( std :: declval < LHS > () + std :: declval < RHS > ()) // samma effekt som med std::forward ovan { return std :: framåt < LHS > ( lhs ) + std :: framåt < RHS > ( rhs ); } mall < typnamn LHS , typnamn RHS > decltype ( std :: declval < LHS > () + std :: declval < RHS > ()) // samma effekt som att sätta typ i slutet AddingFunc ( LHS && lhs , RHS && rhs ) { return std :: framåt < LHS > ( lhs ) + std :: framåt < RHS > ( rhs ); }Den nya syntaxen kan användas i enklare deklarationer och deklarationer:
strukturera SomeStruct { auto FuncName ( int x , int y ) -> int ; }; auto SomeStruct :: FuncName ( int x , int y ) -> int { returnera x + y _ }Användningen av nyckelordet " " autoi detta fall innebär endast en sen indikation av returtypen och är inte relaterad till dess automatiska slutledning.
Förbättra objektkonstruktörerStandard C++ tillåter inte att en klasskonstruktor anropas från en annan konstruktor av samma klass; varje konstruktor måste initiera alla medlemmar i klassen helt, eller anropa klassens metoder för att göra det. Icke-konstmedlemmar i en klass kan inte initieras på den plats där dessa medlemmar deklareras.
C++11 blir av med dessa problem.
Den nya standarden tillåter att en klasskonstruktör anropas från en annan (den så kallade delegeringen). Detta gör att du kan skriva konstruktörer som använder beteendet hos andra konstruktörer utan att införa dubblettkod.
Exempel:
klass SomeType { int nummer ; offentliga : SomeType ( int new_number ) : number ( new_number ) {} SomeType () : SomeType ( 42 ) {} };Från exemplet kan du se att konstruktorn SomeTypeutan argument anropar konstruktorn för samma klass med ett heltalsargument för att initiera variabeln number. En liknande effekt skulle kunna uppnås genom att ange ett initialt värde på 42 för denna variabelrätt vid dess deklaration.
klass SomeType { int nummer = 42 ; offentliga : SomeType () {} explicit SomeType ( int new_number ) : number ( new_number ) {} };Vilken klasskonstruktör som helst kommer att initialiseras numbertill 42 om den inte själv tilldelar den ett annat värde.
Java , C# och D är exempel på språk som också löser dessa problem .
Det bör noteras att om ett objekt i C++03 anses vara helt skapat när dess konstruktör slutför exekveringen, så kommer resten av konstruktörerna i C++11, efter att minst en delegerande konstruktor har exekveras, att arbeta på ett färdigbyggt föremål. Trots detta kommer objekten i den härledda klassen att konstrueras först efter att alla konstruktörer för basklasserna har exekverats.
Explicit ersättning av virtuella funktioner och finalitetDet är möjligt att signaturen för en virtuell metod har ändrats i basklassen eller felaktigt inställd i den härledda klassen initialt. I sådana fall kommer den givna metoden i den härledda klassen inte att åsidosätta motsvarande metod i basklassen. Så om programmeraren inte ändrar metodsignaturen korrekt i alla härledda klasser, kanske metoden inte anropas korrekt under programkörningen. Till exempel:
struct Base { virtuell void some_func (); }; struct härledd : Base { void sone_func (); };Här är namnet på en virtuell funktion som deklareras i en härledd klass felstavat, så en sådan funktion kommer inte att åsidosätta Base::some_func, och kommer därför inte att anropas polymorft genom en pekare eller referens till bassubobjektet.
C++11 kommer att lägga till möjligheten att spåra dessa problem vid kompileringstid (snarare än körtid). För bakåtkompatibilitet är den här funktionen valfri. Den nya syntaxen visas nedan:
struktur B { virtuell void some_func (); virtuell void f ( int ); virtuell void g () const ; }; struktur D1 : offentlig B { void sone_func () åsidosätt ; // fel: ogiltigt funktionsnamn void f ( int ) åsidosätta ; // OK: åsidosätter samma funktion i basklassen virtual void f ( long ) override ; // fel: parametertyp inte matchar virtuell void f ( int ) const åsidosättande ; // fel: funktion cv-kvalificering missmatchar virtuell int f ( int ) åsidosättande ; // fel: returtyp missmatchar virtuell void g () const final ; // OK: åsidosätter samma funktion i basklassen virtual void g ( long ); // OK: ny virtuell funktion }; struktur D2 : Dl { virtuell void g () const ; // fel: försök att ersätta den sista funktionen };Närvaron av en specificator för en virtuell funktion finalinnebär att dess ytterligare ersättning är omöjlig. Dessutom kan en klass som definieras med den slutliga specifikationen inte användas som en basklass:
struct F final { int x , y ; }; struct D : F // fel: arv från slutklasser inte tillåtet { int z ; };Identifierarna overrideoch finalhar en speciell betydelse endast när de används i vissa situationer. I andra fall kan de användas som normala identifierare (till exempel som namn på en variabel eller funktion).
NollpekarkonstantSedan tillkomsten av C 1972 har konstanten 0 spelat den dubbla rollen som ett heltal och en nollpekare. Ett sätt att hantera denna tvetydighet som är inneboende i C-språket är makrot NULL, som vanligtvis utför ((void*)0)eller substitutionen 0. C++ skiljer sig från C i detta avseende, och tillåter endast användningen 0av en nollpekare som en konstant. Detta leder till dålig interaktion med funktionsöverbelastning:
void foo ( char * ); void foo ( int );Om makrot NULLdefinieras som 0(vilket är vanligt i C++), kommer raden foo(NULL);att resultera i ett anrop foo(int), inte foo(char *)som en snabb titt på koden kan antyda, vilket nästan säkert inte är vad programmeraren avsåg.
En av nyheterna med C++11 är ett nytt nyckelord för att beskriva en nollpekarkonstant - nullptr. Denna konstant är av typen std::nullptr_t, som implicit kan konverteras till typen av vilken pekare som helst och jämföras med vilken pekare som helst. Implicit konvertering till en integraltyp är inte tillåten, förutom bool. Det ursprungliga förslaget till standarden tillät inte implicit konvertering till booleskt, men standardutformningsgruppen tillät sådana konverteringar för att vara kompatibla med konventionella pekartyper. Den föreslagna formuleringen ändrades efter en enhällig omröstning i juni 2008 [1] .
För bakåtkompatibilitet kan en konstant 0också användas som en nollpekare.
char * pc = nullptr ; // true int * pi = nullptr ; // true bool b = nullptr ; // höger. b=falskt. int i = nullptr ; // fel foo ( nullptr ); // anropar foo(char *), inte foo(int);Ofta är konstruktioner där visaren garanterat är tom enklare och säkrare än resten - så du kan överbelasta med . nullptr_t
klass Nyttolast ; klass SmartPtr { SmartPtr () = default ; SmartPtr ( nullptr_t ) {} // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< explicit SmartPtr ( nyttolast * aData ) : fData ( aData ) {} // kopiera konstruktorer och op= utelämna ~ SmartPtr () { delete fData ; } privat : Nyttolast * fData = nullptr ; } SmartPtr getPayload1 () { return nullptr ; } // SmartPtr(nullptr_t) överbelastning kommer att anropas. Starkt skrivna enumsI standard C++ är enums inte typsäkra. Faktum är att de representeras av heltal, trots att själva typerna av uppräkningar skiljer sig från varandra. Detta gör det möjligt att göra jämförelser mellan två värden från olika enums. Det enda alternativet som C++03 erbjuder för att skydda enum är att inte implicit konvertera heltal eller element i en enum till element i en annan enum. Dessutom är sättet det representeras i minnet (heltalstyp) implementeringsberoende och därför inte portabelt. Slutligen har uppräkningselement ett gemensamt omfång, vilket gör det omöjligt att skapa element med samma namn i olika uppräkningar.
C++11 erbjuder en speciell klassificering av dessa enums, fri från ovanstående nackdelar. För att beskriva sådana uppräkningar används en deklaration enum class(den kan också användas enum structsom en synonym):
enum class enumeration { Val1 , Val2 , Val3 = 100 , Val4 , /* = 101 */ };En sådan uppräkning är typsäker. Element i en klassenum kan inte implicit konverteras till heltal. Som en konsekvens är jämförelse med heltal också omöjlig (uttrycket Enumeration::Val4 == 101resulterar i ett kompileringsfel).
Klassuppräkningstypen är nu implementeringsoberoende. Som standard, som i fallet ovan, är denna typ int, men i andra fall kan typen ställas in manuellt enligt följande:
enum class Enum2 : unsigned int { Val1 , Val2 };Omfattningen av enum-medlemmar bestäms av omfattningen av enum-namnet. Att använda elementnamn kräver att man specificerar namnet på klassens enum. Så till exempel är värdet Enum2::Val1definierat, men värdet Val1 är inte definierat.
Dessutom erbjuder C++11 möjligheten att explicit scoping och underliggande typer för vanliga enums:
enum Enum3 : unsigned long { Val1 = 1 , Val2 };I det här exemplet är enum-elementnamnen definierade i enum-utrymmet (Enum3::Val1), men för bakåtkompatibilitet är elementnamnen också tillgängliga i det gemensamma omfånget.
Även i C++11 är det möjligt att fördeklarera enums. I tidigare versioner av C++ var detta inte möjligt eftersom storleken på en enum berodde på dess element. Sådana deklarationer kan endast användas när storleken på uppräkningen är specificerad (explicit eller implicit):
enum Enum1 ; // ogiltig för C++ och C++11; underliggande typ kan inte bestämmas enum Enum2 : unsigned int ; // true för C++11, underliggande typ uttryckligen specificerad enum- klass Enum3 ; // true för C++11, underliggande typ är int enum klass Enum4 : unsigned int ; // sant för C++11. enum Enum2 : unsigned short ; // ogiltig för C++11 eftersom Enum2 tidigare deklarerades med en annan underliggande typ VinkelparenteserStandard C++-tolkare definierar alltid teckenkombinationen ">>" som den högra skiftoperatorn. Avsaknaden av ett mellanslag mellan de avslutande vinkelparenteserna i mallparametrarna (om de är kapslade) behandlas som ett syntaxfel.
C++11 förbättrar tolkarens beteende i det här fallet så att flera rätvinkliga parenteser kommer att tolkas som avslutande mallargumentlistor.
Det beskrivna beteendet kan fixas till förmån för det gamla tillvägagångssättet med hjälp av parenteser.
mall < klass T > klass Y { /* ... */ }; Y < X < 1 >> x3 ; // Rätt, samma som "Y<X<1> > x3;". Y < X < 6 >> 1 >> x4 ; // Syntaxfel. Du måste skriva "Y<X<(6>>1)>> x4;".Som visas ovan är denna förändring inte helt kompatibel med den tidigare standarden.
Explicita konverteringsoperatorerC++-standarden tillhandahåller nyckelordet explicitsom en modifierare för enparameterskonstruktörer så att sådana konstruktorer inte fungerar som implicita konverteringskonstruktorer. Detta påverkar dock inte de faktiska konverteringsoperatörerna på något sätt. Till exempel kan en smart pekarklass innehålla operator bool()för att efterlikna en normal pekare. En sådan operator kan till exempel kallas så här: if(smart_ptr_variable)(grenen exekveras om pekaren inte är null). Problemet är att en sådan operatör inte skyddar mot andra oväntade konverteringar. Eftersom typen booldeklareras som en aritmetisk typ i C++, är implicit konvertering till vilken heltalstyp som helst eller till och med till en flyttalstyp möjlig, vilket i sin tur kan leda till oväntade matematiska operationer.
I C++11 explicitgäller nyckelordet även konverteringsoperatorer. Liksom konstruktörer skyddar den mot oväntade implicita omvandlingar. Men situationer där språket kontextuellt förväntar sig en boolesk typ (till exempel i villkorliga uttryck, loopar och logiska operatoroperander) anses vara explicita omvandlingar, och den explicita boolkonverteringsoperatorn anropas direkt.