C++11 [1] [2] eller ISO/IEC 14882:2011 [3] (i processen med at arbejde på standarden havde den kodenavnet C++0x [4] [5] ) — en ny version af C++ sprogstandarden i stedet for den tidligere gyldige ISO /IEC 14882:2003. Den nye standard indeholder tilføjelser til kernen af sproget og en udvidelse til standardbiblioteket, inklusive det meste af TR1 - måske undtagen biblioteket med specielle matematiske funktioner. Nye versioner af standarderne, sammen med nogle andre C++-standardiseringsdokumenter, er offentliggjort på ISO C++-udvalgets websted [6] . C++ programmeringseksempler
Programmeringssprog gennemgår en gradvis udvikling af deres muligheder (i øjeblikket, efter C++11, er følgende standardudvidelser blevet offentliggjort: C++14, C++17, C++20). Denne proces forårsager uundgåeligt kompatibilitetsproblemer med eksisterende kode. Appendiks C.2 [diff.cpp03] i Final Draft International Standard N3290 beskriver nogle af inkompatibiliteterne mellem C++11 og C++03.
Som allerede nævnt vil ændringerne påvirke både C++-kernen og dets standardbibliotek.
Ved udviklingen af hvert afsnit af den fremtidige standard brugte udvalget en række regler:
Der lægges vægt på begyndere, som altid vil udgøre størstedelen af programmører. Mange begyndere søger ikke at uddybe deres viden om C++, idet de begrænser sig til at bruge det, når de arbejder med snævre specifikke opgaver [7] . I betragtning af C++'s alsidighed og bredden af dets brug (inklusive både de mange forskellige applikationer og programmeringsstile), kan selv professionelle finde sig selv nye til nye programmeringsparadigmer .
Udvalgets primære opgave er at udvikle kernen i C++ sproget. Kernen er blevet væsentligt forbedret, multithreading -understøttelse er blevet tilføjet, understøttelse af generisk programmering er blevet forbedret , initialisering er blevet forenet, og der er blevet arbejdet på at forbedre dens ydeevne.
For nemheds skyld er kernefunktionerne og ændringerne opdelt i tre hoveddele: ydeevneforbedringer, bekvemmelighedsforbedringer og ny funktionalitet. Individuelle elementer kan tilhøre flere grupper, men vil kun blive beskrevet i én - den mest passende.
Disse sprogkomponenter introduceres for at reducere hukommelsesomkostninger eller forbedre ydeevnen.
Midlertidige objektreferencer og flyttesemantikIfølge C++-standarden kan et midlertidigt objekt , der er et resultat af evalueringen af et udtryk, overføres til funktioner, men kun ved en konstant reference ( const & ). Funktionen er ikke i stand til at bestemme, om det beståede objekt kan betragtes som midlertidigt og modificerbart (et const-objekt, der også kan videregives af en sådan reference, kan ikke ændres (lovligt)). Dette er ikke et problem for simple strukturer som f.eks complex. , men for komplekse typer, der kræver hukommelsesallokering-deallokering, kan ødelæggelse af et midlertidigt objekt og oprettelse af et permanent være tidskrævende, mens man blot kan sende pointere direkte.
C++11 introducerer en ny type reference , rvalue referencen . Dens erklæring er: type && . Nye regler for overbelastningsopløsning giver dig mulighed for at bruge forskellige overbelastede funktioner til ikke-konstative midlertidige objekter, angivet med rvalues, og for alle andre objekter. Denne innovation gør det muligt at implementere den såkaldte move-semantics .
For eksempel std::vector er en simpel indpakning omkring et C-array og en variabel, der gemmer dens størrelse. Kopikonstruktøren std::vector::vector(const vector &x)vil oprette et nyt array og kopiere informationen; overførselskonstruktøren std::vector::vector(vector &&x)kan simpelthen udveksle pointere og variabler, der indeholder længden.
Annonce eksempel.
skabelon < klasse T > klassevektor _ { vektor ( konst vektor & ); // Kopier konstruktør (langsom) vektor ( vektor && ); // Overfør konstruktør fra et midlertidigt objekt (hurtig) vektor & operator = ( const vektor & ); // Regelmæssig tildeling (langsom) vektor & operator = ( vektor && ); // Flyt midlertidigt objekt (hurtigt) void foo () & ; // Funktion, der kun virker på et navngivet objekt (langsomt) void foo () && ; // Funktion, der kun virker for et midlertidigt objekt (hurtigt) };Der er flere mønstre forbundet med midlertidige links, hvoraf de to vigtigste er og . Den første gør et regulært navngivet objekt til en midlertidig reference: moveforward
// std::move skabelon eksempel void bar ( std :: string && x ) { statisk std :: stringsomeString ; _ someString = std :: move ( x ); // inde i x=string&-funktionen, deraf det andet træk for at kalde flytteopgaven } std :: snorlige ; _ bar ( std :: flyt ( y )); // første træk forvandler streng& til streng&& til opkaldsbjælkenSkabelonen bruges kun i metaprogrammering, kræver en eksplicit skabelonparameter (den har to overbelastninger, der ikke kan skelnes), og er forbundet med to nye C++-mekanismer. Den første er linklimning: , derefter . For det andet kræver bar()-funktionen ovenfor et midlertidigt objekt på ydersiden, men på indersiden er x-parameteren en almindelig navngivet (lvalue) for fallback, hvilket gør det umuligt automatisk at skelne string&-parameteren fra string&&-parameteren. I en almindelig ikke-skabelonfunktion kan programmøren sætte move(), men hvad med skabelonen? forwardusing One=int&&; using Two=One&;Two=int&
// eksempel på brug af skabelonen std::forward class Obj { std :: stringfield ; _ skabelon < classT > _ Obj ( T && x ) : felt ( std :: fremad < T > ( x )) {} };Denne konstruktør dækker de almindelige (T=streng&), kopi- (T=const string&) og flytte- (T=streng) overbelastninger med referencelimning. Og frem gør intet eller udvider til std::move afhængigt af typen af T, og konstruktøren vil kopiere, hvis det er en kopi, og flytte, hvis det er en flytning.
Generiske konstantudtrykC++ har altid haft begrebet konstante udtryk. Således gav udtryk som 3+4 altid de samme resultater uden at forårsage bivirkninger. I sig selv giver konstante udtryk en bekvem måde for C++-kompilere til at optimere resultatet af kompilering. Kompilere evaluerer kun resultaterne af sådanne udtryk på kompileringstidspunktet og gemmer de allerede beregnede resultater i programmet. Sådanne udtryk evalueres således kun én gang. Der er også enkelte tilfælde, hvor sprogstandarden kræver brug af konstante udtryk. Sådanne tilfælde kan for eksempel være definitioner af eksterne arrays eller enum-værdier.
Ovenstående kode er ulovlig i C++, fordi GiveFive() + 7 ikke teknisk set er et konstant udtryk kendt på kompileringstidspunktet. Compileren ved bare ikke på det tidspunkt, at funktionen faktisk returnerer en konstant ved kørselstid. Årsagen til denne compiler-ræsonnement er, at denne funktion kan påvirke tilstanden af en global variabel, kalde en anden ikke-konst runtime-funktion og så videre.
C++11 introducerer nøgleordet constexpr , som giver brugeren mulighed for at sikre, at enten en funktion eller en objektkonstruktør returnerer en kompileringstidskonstant. Ovenstående kode kan omskrives sådan:
constexpr int GiveFive () { return 5 ;} int some_value [ GiveFive () + 7 ]; // opret en matrix med 12 heltal; tilladt i C++11Dette nøgleord giver compileren mulighed for at forstå og verificere, at GiveFive returnerer en konstant.
Brugen af constexpr pålægger meget strenge begrænsninger for funktionens handlinger:
I den tidligere version af standarden kunne kun heltals- eller enum-typevariabler bruges i konstante udtryk. I C++11 ophæves denne begrænsning for variabler, hvis definition er indledt af constexpr-nøgleordet:
constexpr dobbelt accelerationOfGravity = 9,8 ; constexpr dobbeltmåneGravity = accelerationOfGravity / 6 ; _Sådanne variable anses allerede implicit for at være angivet med nøgleordet const . De kan kun indeholde resultaterne af konstante udtryk eller konstruktørerne af sådanne udtryk.
Hvis det er nødvendigt at konstruere konstante værdier fra brugerdefinerede typer, kan konstruktører af sådanne typer også erklæres ved hjælp af constexpr . En konstant udtrykskonstruktør skal ligesom konstantfunktioner også defineres før dens første brug i den aktuelle kompileringsenhed. En sådan konstruktør skal have en tom krop, og en sådan konstruktør skal initialisere medlemmerne af sin type med kun konstanter.
Ændringer i definitionen af simple dataI standard C++ kan kun strukturer, der opfylder et bestemt sæt regler, betragtes som en almindelig gammel datatype ( POD). Der er gode grunde til at forvente, at disse regler udvides, så flere typer betragtes som POD'er. Typer, der opfylder disse regler, kan bruges i en C-kompatibel objektlagsimplementering, men C++03's liste over disse regler er alt for restriktiv.
C++11 vil lempe adskillige regler vedrørende definitionen af simple datatyper.
En klasse anses for at være en simpel datatype, hvis den er triviel , har et standardlayout ( standardlayout ) , og hvis typerne af alle dens ikke-statiske datamedlemmer også er simple datatyper.
En triviel klasse er en klasse, der:
En klasse med standardplacering er en klasse, der:
I standard C++ skal compileren instansiere en skabelon, når den støder på sin fulde specialisering i en oversættelsesenhed. Dette kan øge kompileringstiden markant, især når skabelonen instansieres med de samme parametre i et stort antal oversættelsesenheder. Der er i øjeblikket ingen måde at fortælle C++, at der ikke skal være nogen instansiering.
C++11 introducerede ideen om eksterne skabeloner. C++ har allerede en syntaks til at fortælle compileren, at en skabelon skal instansieres på et bestemt tidspunkt:
skabelon klasse std :: vektor < MyClass > ;C++ mangler evnen til at forhindre compileren i at instansiere en skabelon i en oversættelsesenhed. C++11 udvider simpelthen denne syntaks:
ekstern skabelon klasse std :: vektor < MyClass > ;Dette udtryk fortæller compileren ikke at instansiere skabelonen i denne oversættelsesenhed.
Disse funktioner er beregnet til at gøre sproget lettere at bruge. De giver dig mulighed for at styrke typesikkerheden, minimere kodeduplikering, gøre det sværere for kode at blive misbrugt, og så videre.
InitialiseringslisterBegrebet initialiseringslister kom til C++ fra C. Ideen er, at en struktur eller et array kan oprettes ved at sende en liste med argumenter i samme rækkefølge, som medlemmerne af strukturen er defineret. Initialiseringslister er rekursive, hvilket gør det muligt at bruge dem til arrays af strukturer og strukturer, der indeholder indlejrede strukturer.
struct objekt { flyde først ; int anden ; }; Objekt skalar = { 0.43f , 10 }; // ét objekt, med first=0.43f og second=10 Object anArray [] = {{ 13.4f , 3 }, { 43.28f , 29 }, { 5.934f , 17 }}; // række af tre objekterInitialiseringslister er meget nyttige til statiske lister, og når du vil initialisere en struktur til en bestemt værdi. C++ indeholder også konstruktører, som kan indeholde det generelle arbejde med at initialisere objekter. C++-standarden tillader brug af initialiseringslister for strukturer og klasser, forudsat at de er i overensstemmelse med POD-definitionen (Plain Old Data). Ikke-POD-klasser kan ikke bruge initialiseringslister til initialisering, inklusive standard C++-beholdere såsom vektorer.
C++11 har forbundet konceptet med initialiseringslister og en skabelonklasse kaldet std::initializer_list . Dette gjorde det muligt for konstruktører og andre funktioner at modtage initialiseringslister som parametre. For eksempel:
klasse SequenceClass { offentligt : SequenceClass ( std :: initializer_list < int > list ); };Denne beskrivelse giver dig mulighed for at oprette en SequenceClass ud fra en sekvens af heltal som følger:
SequenceClass someVar = { 1 , 4 , 5 , 6 };Dette demonstrerer, hvordan en speciel slags konstruktør fungerer for en initialiseringsliste. Klasser, der indeholder sådanne konstruktører, behandles på en særlig måde under initialisering (se nedenfor ).
Klassen std::initializer_list<> er defineret i C++11 Standard Library. Objekter af denne klasse kan dog kun oprettes statisk af C++11-kompileren ved hjælp af {}-parentessyntaksen. Listen kan kopieres efter oprettelse, dog vil dette være kopi-for-henvisning. Initialiseringslisten er const: hverken dens medlemmer eller deres data kan ændres efter oprettelsen.
Fordi std::initializer_list<> er en fuldgyldig type, kan den bruges i mere end blot konstruktører. Almindelige funktioner kan tage indtastede initialiseringslister som et argument, for eksempel:
void Funktionsnavn ( std :: initializer_list < float > list ); Funktionsnavn ({ 1.0f , -3.45f , -0.4f });Standardbeholdere kan initialiseres på denne måde:
std :: vektor < std :: streng > v = { "xyzzy" , "plugh" , "abracadabra" }; std :: vektor < std :: streng > v { "xyzzy" , "plugh" , "abracadabra" }; Generisk initialiseringC++-standarden indeholder en række problemer relateret til typeinitialisering. Der er flere måder at initialisere typer på, og ikke alle fører til de samme resultater. For eksempel kan den traditionelle syntaks for en initialiserende konstruktør ligne en funktionsdeklaration, og der skal udvises ekstra forsigtighed for at forhindre compileren i at analysere den forkert. Kun aggregattyper og POD-typer kan initialiseres med aggregatinitialiserere (af typen SomeType var = {/*stuff*/};).
C++11 giver en syntaks, der gør det muligt at bruge en enkelt form for initialisering til alle slags objekter ved at udvide initialiseringslistens syntaks:
struct BasicStruct { int x ; dobbelt y ; }; struct AltStruct { AltStruct ( int x , double y ) : x_ ( x ), y_ ( y ) {} privat : int x_ ; dobbelt y_ ; }; BasicStruct var1 { 5 , 3.2 }; AltStruct var2 { 2 , 4.3 };Initialisering af var1 fungerer nøjagtigt på samme måde som initialisering af aggregater, det vil sige, at hvert objekt initialiseres ved at kopiere den tilsvarende værdi fra initialiseringslisten. Om nødvendigt vil implicit typekonvertering blive anvendt. Hvis den ønskede transformation ikke eksisterer, vil kildekoden blive betragtet som ugyldig. Under initialiseringen af var2 vil konstruktøren blive kaldt.
Det er muligt at skrive kode som denne:
struktur IdString { std :: strengnavn ; _ int identifikator ; }; IdString GetString () { returner { "SomeName" , 4 }; // Bemærk manglen på eksplicitte typer }Generisk initialisering erstatter ikke fuldstændigt konstruktørinitialiseringssyntaks. Hvis en klasse har en konstruktør, der tager en initialiseringsliste ( TypeName(initializer_list<SomeType>); ) som et argument, vil den have forrang over andre muligheder for oprettelse af objekter. For eksempel, i C++11 indeholder std::vector en konstruktør, der tager en initialiseringsliste som et argument:
std :: vektor < int > theVec { 4 };Denne kode vil resultere i et konstruktørkald, der tager en initialiseringsliste som et argument, snarere end en én-parameter konstruktør, der opretter en container af den givne størrelse. For at kalde denne konstruktør skal brugeren bruge standard konstruktørinvokationssyntaksen.
Indtast inferensI standard C++ (og C) skal typen af en variabel angives eksplicit. Men med fremkomsten af skabelontyper og skabelonmetaprogrammeringsteknikker kan typen af nogle værdier, især funktionsreturværdier, ikke let specificeres. Dette fører til vanskeligheder med at lagre mellemliggende data i variabler, nogle gange kan det være nødvendigt at kende den interne struktur af et bestemt metaprogrammeringsbibliotek.
C++11 tilbyder to måder at afhjælpe disse problemer på. For det første kan definitionen af en eksplicit initialiserbar variabel indeholde nøgleordet auto . Dette vil resultere i oprettelsen af en variabel af typen initialiseringsværdi:
auto someStrangeCallableType = std :: bind ( & SomeFunction , _2 , _1 , someObject ); auto andenVariabel = 5 ;Typen someStrangeCallableType bliver den type, som den konkrete implementering af skabelonfunktionen returnerer std::bindfor de givne argumenter. Denne type vil let blive bestemt af compileren under semantisk analyse, men programmøren ville være nødt til at lave noget research for at bestemme typen.
Den andenVariable -type er også veldefineret, men kan lige så nemt defineres af programmøren. Denne type er int , det samme som en heltalskonstant.
Derudover kan nøgleordet decltype bruges til at bestemme typen af et udtryk på kompileringstidspunktet . For eksempel:
int nogleInt ; decltype ( someInt ) otherIntegerVariable = 5 ;Brug af decltype er mest nyttigt i forbindelse med auto , da typen af en variabel, der er erklæret som auto , kun er kendt af compileren. Brug af decltype kan også være ret nyttigt i udtryk, der bruger operatøroverbelastning og skabelonspecialisering.
autokan også bruges til at reducere koderedundans. For eksempel i stedet for:
for ( vektor < int >:: const_iterator itr = myvec . cbegin (); itr != myvec . cend (); ++ itr )programmøren kan skrive:
for ( auto itr = myvec . cbegin (); itr != myvec . cend (); ++ itr )Forskellen bliver især mærkbar, når en programmør bruger et stort antal forskellige containere, selvom der stadig er en god måde at reducere overflødig kode ved at bruge typedef.
En type, der er markeret med decltype , kan være forskellig fra den type, der udledes med auto .
#inkluder <vektor> int main () { const std :: vektor < int > v ( 1 ); auto a = v [ 0 ]; // type a - int decltype ( v [ 0 ]) b = 1 ; // type b - const int& (returværdi // std::vector<int>::operator[](size_type) const) auto c = 0 ; // skriv c - int auto d = c ; // type d - int decltype ( c ) e ; // type e - int, type enhed med navnet c decltype (( c )) f = c ; // type f er int& fordi (c) er en lværdi decltype ( 0 ) g ; // type g er int, da 0 er en rværdi } For-loop gennem en samlingI standard C++ kræver det en masse kode at iterere over elementerne i en samling . Nogle sprog, såsom C# , har faciliteter, der giver en " foreach " -sætning , der automatisk går gennem elementerne i en samling fra start til slut. C++11 introducerer en lignende facilitet. For - sætningen gør det lettere at iterere over en samling af elementer:
int my_array [ 5 ] = { 1 , 2 , 3 , 4 , 5 }; for ( int & x : my_array ) { x *= 2 ; }Denne form for for, kaldet "range-based for" på engelsk, vil besøge hvert element i samlingen. Dette gælder for C -arrays , initialiseringslister og alle andre typer , der har funktioner, begin()og end()som returnerer iteratorer . Alle containere i standardbiblioteket , der har et start/slut-par, vil arbejde med en for-erklæring på samlingen.
En sådan cyklus vil også fungere, for eksempel med C-lignende arrays, fordi C++11 introducerer kunstigt de nødvendige pseudo-metoder for dem (begyndelse, slutning og nogle andre).
// interval-baseret gennemgang af den klassiske matrix int arr1 [] = { 1 , 2 , 3 }; for ( auto el : arr1 ); Lambda funktioner og udtrykI standard C++, for eksempel, når man bruger standard C++ biblioteksalgoritmerne sorter og find , er der ofte behov for at definere prædikatfunktioner i nærheden af, hvor algoritmen kaldes. Der er kun én mekanisme i sproget til dette: evnen til at definere en funktionsklasse (det er forbudt at overføre en forekomst af en klasse defineret inde i en funktion til algoritmer (Meyers, Effektiv STL)). Ofte er denne metode for overflødig og omfattende og gør det kun svært at læse koden. Derudover tillader standard C++-reglerne for klasser defineret i funktioner ikke, at de kan bruges i skabeloner og gør dem dermed umulige at bruge.
Den åbenlyse løsning på problemet var at tillade definitionen af lambda-udtryk og lambda-funktioner i C++11. Lambdafunktionen er defineret sådan:
[]( int x , int y ) { return x + y ; }Returtypen for denne unavngivne funktion beregnes som decltype(x+y) . Returtypen kan kun udelades, hvis lambda-funktionen er af formen . Dette begrænser størrelsen af lambda-funktionen til et enkelt udtryk. return expression
Returtypen kan angives eksplicit, for eksempel:
[]( int x , int y ) -> int { int z = x + y ; returnere z ; }Dette eksempel opretter en midlertidig variabel z for at gemme en mellemværdi. Som med normale funktioner bevares denne mellemværdi ikke mellem opkald.
Returtypen kan helt udelades, hvis funktionen ikke returnerer en værdi (det vil sige, at returtypen er void )
Det er også muligt at bruge referencer til variable defineret i samme omfang som lambda-funktionen. Et sæt af sådanne variabler kaldes normalt en lukning . Lukninger defineres og bruges som følger:
std :: vektor < int > someList ; int total = 0 ; std :: for_each ( someList . begin (), someList . end (), [ & total ]( int x ) { total += x ; }); std :: cout << total ;Dette vil vise summen af alle elementer på listen. Den samlede variabel gemmes som en del af lambdafunktionens lukning. Fordi den refererer til stackvariablen total , kan den ændre dens værdi.
Lukningsvariabler for lokale variabler kan også defineres uden brug af referencesymbolet & , hvilket betyder at funktionen kopierer værdien. Dette tvinger brugeren til at erklære en hensigt om at henvise til eller kopiere en lokal variabel.
For lambda-funktioner, der med garanti udføres i deres omfang, er det muligt at bruge alle stakvariabler uden behov for eksplicitte referencer til dem:
std :: vektor < int > someList ; int total = 0 ; std :: for_each ( someList . begin (), someList . end (), [ & ]( int x ) { total += x ; });Implementeringsmetoder kan variere internt, men lambda-funktionen forventes at gemme en pointer til stakken af den funktion, den blev oprettet i, i stedet for at operere på individuelle stackvariablereferencer.
[&]Hvis det bruges i stedet [=], vil alle anvendte variable blive kopieret, hvilket gør det muligt at bruge lambda-funktionen uden for de oprindelige variables omfang.
Standardoverførselsmetoden kan også suppleres med en liste over individuelle variabler. For eksempel, hvis du skal videregive de fleste variabler ved reference, og en efter værdi, kan du bruge følgende konstruktion:
int total = 0 ; int værdi = 5 ; [ & , værdi ]( int x ) { total += ( x * værdi ); } ( 1 ); //(1) Kald lambda-funktion med værdi 1Dette vil medføre, at total overføres ved reference og værdi efter værdi.
Hvis en lambda-funktion er defineret i en klassemetode, betragtes den som en ven af den klasse. Sådanne lambda-funktioner kan bruge en reference til et objekt af klassetypen og få adgang til dets interne felter:
[]( SomeType * typePtr ) { typePtr -> SomePrivateMemberFunction (); }Dette vil kun fungere, hvis omfanget af lambda-funktionen er en klassemetode SomeType .
Arbejdet med denne pointer til det objekt, som den aktuelle metode interagerer med, implementeres på en særlig måde. Det skal udtrykkeligt markeres i lambda-funktionen:
[ this ]() { this -> SomePrivateMemberFunction (); }Brug af en formular [&]eller [=]en lambda-funktion gør denne tilgængelig automatisk.
Typen af lambda-funktioner er implementeringsafhængig; navnet på denne type er kun tilgængeligt for compileren. Hvis du skal sende en lambda-funktion som en parameter, skal den være en skabelontype eller lagres ved hjælp af std::function . Autonøgleordet giver dig mulighed for at gemme en lambda-funktion lokalt :
auto myLambdaFunc = [ this ]() { this -> SomePrivateMemberFunction (); };Derudover, hvis funktionen ikke tager nogen argumenter, kan ()du udelade:
auto myLambdaFunc = []{ std :: cout << "hej" << std :: endl ; }; Alternativ funktionssyntaksNogle gange er der behov for at implementere en funktionsskabelon, der ville resultere i et udtryk, der har samme type og samme værdikategori som et andet udtryk.
skabelon < typenavn LHS , typenavn RHS > RETURN_TYPE AddingFunc ( const LHS & lhs , const RHS & rhs ) // hvad skal RETURN_TYPE være? { retur lhs + rhs ; }For at udtrykket AddingFunc(x, y) skal have samme type og samme værdikategori som udtrykket lhs + rhs , når de gives argumenterne x og y , kan følgende definition bruges i C++11:
skabelon < typenavn LHS , typenavn RHS > decltype ( std :: declval < const LHS &> () + std :: declval < const RHS &> ()) AddingFunc ( const LHS & lhs , const RHS & rhs ) { retur lhs + rhs ; }Denne notation er noget besværlig, og det ville være rart at kunne bruge lhs og rhs i stedet for henholdsvis std::declval<const LHS &>() og std::declval<const RHS &>(). Dog i næste version
skabelon < typenavn LHS , typenavn RHS > decltype ( lhs + rhs ) AddingFunc ( const LHS & lhs , const RHS & rhs ) // Ikke gyldig i C++11 { retur lhs + rhs ; }mere læsbare for mennesker kan lhs- og rhs-identifikatorerne, der bruges i decltype -operanden, ikke angive muligheder, der er erklæret senere. For at løse dette problem introducerer C++11 en ny syntaks til at erklære funktioner med en returtype i slutningen:
skabelon < typenavn LHS , typenavn RHS > auto AddingFunc ( const LHS & lhs , const RHS & rhs ) -> decltype ( lhs + rhs ) { retur lhs + rhs ; }Det skal dog bemærkes, at i den mere generiske AddingFunc-implementering nedenfor, har den nye syntaks ikke fordel af korthed:
skabelon < typenavn LHS , typenavn RHS > auto AddingFunc ( LHS && lhs , RHS && rhs ) -> decltype ( std :: fremad < LHS > ( lhs ) + std :: fremad < RHS > ( rhs )) { return std :: fremad < LHS > ( lhs ) + std :: fremad < RHS > ( rhs ); } skabelon < typenavn LHS , typenavn RHS > auto AddingFunc ( LHS && lhs , RHS && rhs ) -> decltype ( std :: declval < LHS > () + std :: declval < RHS > ()) // samme effekt som med std::forward ovenfor { return std :: fremad < LHS > ( lhs ) + std :: fremad < RHS > ( rhs ); } skabelon < typenavn LHS , typenavn RHS > decltype ( std :: declval < LHS > () + std :: declval < RHS > ()) // samme effekt som at sætte type i slutningen AddingFunc ( LHS && lhs , RHS && rhs ) { return std :: fremad < LHS > ( lhs ) + std :: fremad < RHS > ( rhs ); }Den nye syntaks kan bruges i enklere erklæringer og erklæringer:
struct SomeStruct { auto FuncName ( int x , int y ) -> int ; }; auto SomeStruct :: FuncName ( int x , int y ) -> int { returner x + y _ }Brugen af nøgleordet " " autobetyder i dette tilfælde kun en sen indikation af returtypen og er ikke relateret til dens automatiske slutning.
Forbedring af objektkonstruktørerStandard C++ tillader ikke, at en klassekonstruktør kaldes fra en anden konstruktør af samme klasse; hver konstruktør skal fuldt initialisere alle medlemmer af klassen eller kalde klassens metoder for at gøre det. Ikke-konst-medlemmer af en klasse kan ikke initialiseres på det sted, hvor disse medlemmer er erklæret.
C++11 slipper af med disse problemer.
Den nye standard gør det muligt at kalde en klassekonstruktør fra en anden (den såkaldte delegation). Dette giver dig mulighed for at skrive konstruktører, der bruger adfærd fra andre konstruktører uden at indføre duplikatkode.
Eksempel:
klasse SomeType { int nummer ; offentligt : SomeType ( int new_number ) : number ( new_number ) {} SomeType () : SomeType ( 42 ) {} };Fra eksemplet kan du se, at konstruktøren SomeTypeuden argumenter kalder konstruktøren af den samme klasse med et heltalsargument for at initialisere variablen number. En lignende effekt kunne opnås ved at angive en startværdi på 42 for denne variable ret ved dens erklæring.
klasse SomeType { int tal = 42 ; offentligt : SomeType () {} eksplicit SomeType ( int new_number ) : number ( new_number ) {} };Enhver klassekonstruktør vil initialisere numbertil 42, hvis den ikke selv tildeler en anden værdi til den.
Java , C# og D er eksempler på sprog, der også løser disse problemer .
Det skal bemærkes, at hvis et objekt i C++03 anses for at være fuldt oprettet, når dets konstruktør afslutter eksekveringen, så vil resten af konstruktørerne i C++11, efter at mindst én delegerende konstruktør er blevet udført, arbejde på et fuldt konstrueret objekt. På trods af dette vil objekterne i den afledte klasse kun blive konstrueret, efter at alle konstruktørerne af basisklasserne er blevet udført.
Eksplicit substitution af virtuelle funktioner og endelighedDet er muligt, at signaturen for en virtuel metode er blevet ændret i basisklassen eller forkert indstillet i den afledte klasse oprindeligt. I sådanne tilfælde vil den givne metode i den afledte klasse ikke tilsidesætte den tilsvarende metode i basisklassen. Så hvis programmøren ikke ændrer metodesignaturen korrekt i alle afledte klasser, kaldes metoden muligvis ikke korrekt under programafvikling. For eksempel:
struct Base { virtual void some_func (); }; struct Afledt : Base { void sone_func (); };Her er navnet på en virtuel funktion erklæret i en afledt klasse stavet forkert, så en sådan funktion vil ikke tilsidesætte Base::some_func, og vil derfor ikke blive kaldt polymorf gennem en pointer eller reference til grundsubobjektet.
C++11 vil tilføje muligheden for at spore disse problemer på kompileringstidspunktet (i stedet for køretid). For bagudkompatibilitet er denne funktion valgfri. Den nye syntaks er vist nedenfor:
struktur B { virtual void some_func (); virtuelt tomrum f ( int ); virtuel void g () const ; }; struktur D1 : offentlig B { void sone_func () tilsidesætte ; // fejl: ugyldigt funktionsnavn void f ( int ) tilsidesættelse ; // OK: tilsidesætter den samme funktion i basisklassen virtual void f ( long ) override ; // fejl: parameter type mismatch virtual void f ( int ) const override ; // fejl: funktion cv-kvalifikation mismatch virtuel int f ( int ) tilsidesættelse ; // fejl: returtype mismatch virtual void g () const final ; // OK: tilsidesætter den samme funktion i basisklassen virtual void g ( long ); // OK: ny virtuel funktion }; struktur D2 : D1 { virtuel void g () const ; // fejl: forsøg på at erstatte den endelige funktion };Tilstedeværelsen af en specifikation for en virtuel funktion finalbetyder, at dens yderligere udskiftning er umulig. En klasse defineret med den endelige specificator kan heller ikke bruges som en basisklasse:
struct F final { int x , y ; }; struct D : F // fejl: arv fra afsluttende klasser er ikke tilladt { int z ; };Identifikatorerne og har kun en overridesærlig finalbetydning, når de bruges i visse situationer. I andre tilfælde kan de bruges som normale identifikatorer (for eksempel som navnet på en variabel eller funktion).
Nul pointer konstantSiden fremkomsten af C i 1972 har konstanten 0 spillet den dobbelte rolle som et heltal og en nul-pointer. En måde at håndtere denne tvetydighed, der er iboende i C-sproget, er makroen NULL, som typisk udfører ((void*)0)eller -substitutionen 0. C++ adskiller sig fra C i denne henseende og tillader kun brugen 0af en nul-pointer som en konstant. Dette fører til dårlig interaktion med funktionsoverbelastning:
void foo ( char * ); void foo ( int );Hvis makroen NULLer defineret som 0(hvilket er almindeligt i C++), vil linjen foo(NULL);resultere i et opkald foo(int), ikke foo(char *)som et hurtigt kig på koden kunne antyde, hvilket næsten helt sikkert ikke er, hvad programmøren havde til hensigt.
En af nyhederne ved C++11 er et nyt nøgleord til at beskrive en nul-pointer-konstant - nullptr. Denne konstant er af typen std::nullptr_t, som implicit kan konverteres til typen af enhver pointer og sammenlignes med enhver pointer. Implicit konvertering til en integraltype er ikke tilladt, undtagen for bool. Det oprindelige forslag til standarden tillod ikke implicit konvertering til boolesk, men standardudkastgruppen tillod sådanne konverteringer af hensyn til kompatibilitet med konventionelle pointertyper. Den foreslåede formulering blev ændret efter en enstemmig afstemning i juni 2008 [1] .
For bagudkompatibilitet kan en konstant 0også bruges som en nul-pointer.
char * pc = nullptr ; // true int * pi = nullptr ; // true bool b = nullptr ; // ret. b=falsk. int i = nullptr ; // fejl foo ( nullptr ); // kalder foo(char *), ikke foo(int);Ofte er konstruktioner, hvor viseren med garanti er tom, enklere og mere sikre end resten - så du kan overbelaste med . nullptr_t
klasse Nyttelast ; klasse SmartPtr { SmartPtr () = default ; SmartPtr ( nullptr_t ) {} // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< eksplicit SmartPtr ( Nyttelast * aData ) : fData ( aData ) {} // kopier konstruktører og op= udelad ~ SmartPtr () { delete fData ; } privat : Nyttelast * fData = nullptr ; } SmartPtr getPayload1 () { return nullptr ; } // SmartPtr(nullptr_t) overbelastning vil blive kaldt. Stærkt indtastede enumsI standard C++ er enums ikke typesikre. Faktisk er de repræsenteret af heltal, på trods af at selve typerne af opregninger er forskellige fra hinanden. Dette gør det muligt at foretage sammenligninger mellem to værdier fra forskellige enums. Den eneste mulighed, som C++03 tilbyder for at beskytte enums, er ikke implicit at konvertere heltal eller elementer af en enum til elementer af en anden enum. Den måde, den er repræsenteret i hukommelsen (heltalstype), er også implementeringsafhængig og derfor ikke bærbar. Endelig har opregningselementer et fælles omfang, hvilket gør det umuligt at oprette elementer med samme navn i forskellige opregninger.
C++11 tilbyder en særlig klassificering af disse enums, fri for ovennævnte ulemper. For at beskrive sådanne opregninger bruges en erklæring enum class(den kan også bruges enum structsom et synonym):
enum class enumeration { Val1 , Val2 , Val3 = 100 , Val4 , /* = 101 */ };En sådan opregning er typesikker. Elementer i en klasseenum kan ikke implicit konverteres til heltal. Som en konsekvens er sammenligning med heltal også umulig (udtrykket Enumeration::Val4 == 101resulterer i en kompileringsfejl).
Klasseopregningstypen er nu implementeringsuafhængig. Som standard, som i tilfældet ovenfor, er denne type int, men i andre tilfælde kan typen indstilles manuelt som følger:
enum klasse Enum2 : unsigned int { Val1 , Val2 };Omfanget af enum-medlemmer bestemmes af omfanget af enum-navnet. Brug af elementnavne kræver specificering af navnet på klassens enum. Så for eksempel er værdien Enum2::Val1defineret, men værdien Val1 er ikke defineret.
Derudover giver C++11 muligheden for eksplicit at scoping og underliggende typer for almindelige enums:
enum Enum3 : unsigned long { Val1 = 1 , Val2 };I dette eksempel er enum-elementnavnene defineret i enum-rummet (Enum3::Val1), men for bagudkompatibilitet er elementnavnene også tilgængelige i det fælles omfang.
Også i C++11 er det muligt at foruderklære enums. I tidligere versioner af C++ var dette ikke muligt, fordi størrelsen af en enum afhang af dens elementer. Sådanne erklæringer kan kun bruges, når størrelsen af opregningen er angivet (eksplicit eller implicit):
enum Enum1 ; // ugyldig for C++ og C++11; underliggende type kan ikke bestemmes enum Enum2 : unsigned int ; // true for C++11, underliggende type eksplicit specificeret enum -klasse Enum3 ; // true for C++11, den underliggende type er int enum klasse Enum4 : unsigned int ; // sand for C++11. enum Enum2 : unsigned short ; // ugyldig for C++11, fordi Enum2 tidligere blev erklæret med en anden underliggende type VinkelparenteserStandard C++-parsere definerer altid ">>"-tegnkombinationen som den højre skiftoperator. Fraværet af et mellemrum mellem de afsluttende vinkelparenteser i skabelonparametrene (hvis de er indlejret) behandles som en syntaksfejl.
C++11 forbedrer opførselen af parseren i dette tilfælde, så flere retvinklede parenteser vil blive fortolket som afsluttende skabelonargumentlister.
Den beskrevne adfærd kan rettes til fordel for den gamle tilgang ved hjælp af parenteser.
skabelon < klasse T > klasse Y { /* ... */ }; Y < X < 1 >> x3 ; // Korrekt, samme som "Y<X<1> > x3;". Y < X < 6 >> 1 >> x4 ; // Syntaks fejl. Du skal skrive "Y<X<(6>>1)>> x4;".Som vist ovenfor er denne ændring ikke helt kompatibel med den tidligere standard.
Eksplicitte konverteringsoperatorerC++-standarden giver nøgleordet explicitsom en modifikator for én-parameter-konstruktører, så sådanne konstruktører ikke fungerer som implicitte konverterings-konstruktører. Dette påvirker dog ikke de faktiske konverteringsoperatører på nogen måde. For eksempel kan en smart pointer-klasse indeholde operator bool()for at efterligne en normal pointer. En sådan operator kan for eksempel kaldes sådan: if(smart_ptr_variable)(grenen udføres, hvis markøren ikke er nul). Problemet er, at en sådan operatør ikke beskytter mod andre uventede konverteringer. Da typen booler erklæret som en aritmetisk type i C++, er implicit konvertering til enhver heltalstype eller endda til en flydende kommatype mulig, hvilket igen kan føre til uventede matematiske operationer.
I C++11 explicitgælder nøgleordet også for konverteringsoperatører. Ligesom konstruktører beskytter den mod uventede implicitte konverteringer. Situationer, hvor sproget kontekstuelt forventer en boolsk type (for eksempel i betingede udtryk, sløjfer og logiske operatoroperander) betragtes som eksplicitte konverteringer, og den eksplicitte bool-konverteringsoperator påkaldes direkte.