C++11 [1] [2] sau ISO/IEC 14882:2011 [3] (în procesul de lucru la standard avea numele de cod C++0x [4] [5] ) — o nouă versiune a standardul de limbaj C++ în loc de ISO /IEC 14882:2003 valabil anterior. Noul standard include completări la nucleul limbajului și o extensie la biblioteca standard, inclusiv cea mai mare parte a TR1 - cu excepția probabil bibliotecii de funcții matematice speciale. Noile versiuni ale standardelor, împreună cu alte documente de standardizare C++, sunt publicate pe site-ul web al comitetului ISO C++ [6] . Exemple de programare C++
Limbajele de programare suferă o dezvoltare treptată a capabilităților lor (în prezent, după C++11, au fost publicate următoarele extensii standard: C++14, C++17, C++20). Acest proces provoacă inevitabil probleme de compatibilitate cu codul existent. Anexa C.2 [diff.cpp03] la Final Draft International Standard N3290 descrie unele dintre incompatibilitățile dintre C++11 și C++03.
După cum sa menționat deja, modificările vor afecta atât nucleul C++, cât și biblioteca sa standard.
În dezvoltarea fiecărei secțiuni a viitorului standard, comitetul a folosit o serie de reguli:
Se acordă atenție începătorilor, care vor constitui întotdeauna majoritatea programatorilor. Mulți începători nu caută să-și aprofundeze cunoștințele despre C++, limitându-se la a-l folosi atunci când lucrează la sarcini specifice înguste [7] . În plus, având în vedere versatilitatea C++ și amploarea utilizării acestuia (inclusiv atât varietatea de aplicații, cât și stiluri de programare), chiar și profesioniștii se pot găsi noi în noile paradigme de programare .
Sarcina principală a comitetului este de a dezvolta nucleul limbajului C++. Nucleul a fost îmbunătățit semnificativ, a fost adăugat suport pentru mai multe fire , a fost îmbunătățit suportul pentru programarea generică , inițializarea a fost unificată și s-a lucrat pentru a-și îmbunătăți performanța.
Pentru comoditate, caracteristicile și modificările kernelului sunt împărțite în trei părți principale: îmbunătățiri de performanță, îmbunătățiri de confort și funcționalitate nouă. Elementele individuale pot aparține mai multor grupuri, dar vor fi descrise doar într-una singură - cea mai potrivită.
Aceste componente ale limbajului sunt introduse pentru a reduce supraîncărcarea memoriei sau pentru a îmbunătăți performanța.
Referințe temporare la obiecte și semantică de mutareConform standardului C++ , un obiect temporar rezultat din evaluarea unei expresii poate fi trecut la funcții, dar numai printr-o referință constantă ( const & ). Funcția nu poate determina dacă obiectul transmis poate fi considerat temporar și modificabil (un obiect const care poate fi trecut și printr-o astfel de referință nu poate fi modificat (legal)). Aceasta nu este o problemă pentru structuri simple precum complex, ci pentru tipurile complexe care necesită alocare-dealocare de memorie, distrugerea unui obiect temporar și crearea unuia permanent poate fi consumatoare de timp, în timp ce s-ar putea trece pur și simplu pointeri direct.
C++11 introduce un nou tip de referință , referința rvalue . Declarația sa este: tip && . Noile reguli de rezoluție a supraîncărcării vă permit să utilizați diferite funcții supraîncărcate pentru obiecte temporare non-const, notate cu valorile r, și pentru toate celelalte obiecte. Această inovație permite implementarea așa-numitei semantici de mișcare .
De exemplu, std::vector este un simplu wrapper în jurul unui C-array și o variabilă care stochează dimensiunea acesteia. Constructorul de copiere std::vector::vector(const vector &x)va crea o nouă matrice și va copia informațiile; constructorul de transfer std::vector::vector(vector &&x)poate schimba pur și simplu indicatori și variabile care conțin lungimea.
Exemplu de anunț.
template < clasa T > vector de clasă { vector ( const vector & ); // Copiați constructorul (lent) vector ( vector && ); // Transfer constructor dintr-un obiect temporar (rapid) vector & operator = ( const vector & ); // Atribuire regulată (lent) vector & operator = ( vector && ); // Mută obiectul temporar (rapid) void foo () & ; // Funcție care funcționează numai pe un obiect numit (lent) void foo () && ; // Funcție care funcționează numai pentru un obiect temporar (rapid) };Există mai multe modele asociate cu legăturile temporare, dintre care cele mai importante sunt și . Primul face dintr-un obiect numit obișnuit o referință temporară: moveforward
// std::move template model void bar ( std :: string && x ) { static std :: stringsomeString ; _ someString = std :: mutare ( x ); // în interiorul funcției x=șir&, de unde a doua mișcare pentru a apela atribuirea de mutare } std :: stringy ; _ bară ( std :: mutare ( y )); // prima mișcare transformă string& în string&& la bara de apeluriȘablonul este folosit doar în metaprogramare, necesită un parametru de șablon explicit (are două supraîncărcări care nu se pot distinge) și este asociat cu două mecanisme C++ noi. Prima este lipirea linkurilor: , apoi . În al doilea rând, funcția bar() de mai sus necesită un obiect temporar în exterior, dar în interior, parametrul x este un nume obișnuit (lvalue) pentru fallback, ceea ce face imposibilă distingerea automată a parametrului șir& de parametrul șir&&. Într-o funcție obișnuită non-șablon, programatorul poate sau nu pune move(), dar cum rămâne cu șablonul? forwardusing One=int&&; using Two=One&;Two=int&
// exemplu de utilizare a șablonului std::forward class Obj { std :: stringfield ; _ șablon < classT > _ Obj ( T && x ) : câmp ( std :: înainte < T > ( x )) {} };Acest constructor acoperă supraîncărcările obișnuite (T=șir&), copiere (T=șir constant&) și mutare (T=șir) cu lipirea de referință. Și forward nu face nimic sau se extinde la std::move în funcție de tipul de T, iar constructorul va copia dacă este o copie și se va muta dacă este o mișcare.
Expresii constante genericeC++ a avut întotdeauna conceptul de expresii constante. Astfel, expresii precum 3+4 au returnat întotdeauna aceleași rezultate fără a provoca efecte secundare. Prin ele însele, expresiile constante oferă o modalitate convenabilă pentru compilatorii C++ de a optimiza rezultatul compilării. Compilatorii evaluează rezultatele unor astfel de expresii numai în timpul compilării și stochează rezultatele deja calculate în program. Astfel, astfel de expresii sunt evaluate o singură dată. Există, de asemenea, câteva cazuri în care standardul lingvistic necesită utilizarea expresiilor constante. Astfel de cazuri, de exemplu, pot fi definiții ale matricelor externe sau ale valorilor enumerate.
Codul de mai sus este ilegal în C++ deoarece GiveFive() + 7 nu este, din punct de vedere tehnic, o expresie constantă cunoscută la momentul compilării. Compilatorul pur și simplu nu știe la momentul respectiv că funcția returnează de fapt o constantă în timpul rulării. Motivul pentru acest raționament al compilatorului este că această funcție poate afecta starea unei variabile globale, poate apela o altă funcție de rulare non-const și așa mai departe.
C++11 introduce cuvântul cheie constexpr , care permite utilizatorului să se asigure că fie o funcție, fie un constructor de obiect returnează o constantă de timp de compilare. Codul de mai sus poate fi rescris astfel:
constexpr int GiveFive () { return 5 ;} int some_value [ Dă cinci () + 7 ]; // creează o matrice de 12 numere întregi; permis în C++11Acest cuvânt cheie permite compilatorului să înțeleagă și să verifice dacă GiveFive returnează o constantă.
Utilizarea constexpr impune restricții foarte stricte asupra acțiunilor funcției:
În versiunea anterioară a standardului, numai variabilele de tip întreg sau enumerare puteau fi utilizate în expresiile constante. În C++11, această restricție este ridicată pentru variabilele a căror definiție este precedată de cuvântul cheie constexpr:
constexpr accelerationOfGravity dublă = 9,8 ; constexpr double moonGravity = accelerationOfGravity / 6 ;Astfel de variabile sunt deja implicit considerate a fi notate prin cuvântul cheie const . Ele pot conține doar rezultatele expresiilor constante sau constructorii unor astfel de expresii.
Dacă este necesar să se construiască valori constante din tipuri definite de utilizator, constructorii de astfel de tipuri pot fi, de asemenea, declarați folosind constexpr . Un constructor de expresii constante, ca și funcțiile constante, trebuie de asemenea definit înainte de prima utilizare în unitatea de compilare curentă. Un astfel de constructor trebuie să aibă un corp gol, iar un astfel de constructor trebuie să inițializeze membrii tipului său doar cu constante.
Modificări în definiția datelor simpleÎn C++ standard, numai structurile care îndeplinesc un anumit set de reguli pot fi considerate un tip de date simplu vechi ( POD). Există motive întemeiate să ne așteptăm ca aceste reguli să fie extinse, astfel încât mai multe tipuri să fie considerate POD. Tipurile care îndeplinesc aceste reguli pot fi utilizate într-o implementare a stratului de obiecte compatibile cu C. Cu toate acestea, lista C++03 a acestor reguli este excesiv de restrictivă.
C++11 va relaxa mai multe reguli privind definirea tipurilor de date simple.
O clasă este considerată un tip de date simplu dacă este trivială , are un aspect standard ( standard-layout ) și dacă tipurile tuturor membrilor săi de date nestatice sunt, de asemenea, tipuri de date simple.
O clasă banală este o clasă care:
O clasă cu plasare standard este o clasă care:
În C++ standard, compilatorul trebuie să instanțieze un șablon ori de câte ori își întâlnește specializarea completă într-o unitate de traducere. Acest lucru poate crește semnificativ timpul de compilare, mai ales atunci când șablonul este instanțiat cu aceiași parametri într-un număr mare de unități de traducere. În prezent, nu există nicio modalitate de a spune C++ că nu ar trebui să existe instanțiere.
C++11 a introdus ideea șabloanelor externe. C++ are deja o sintaxă pentru a spune compilatorului că un șablon ar trebui să fie instanțiat la un anumit punct:
template class std :: vector < MyClass > ;C++ nu are capacitatea de a împiedica compilatorul să instanțieze un șablon într-o unitate de traducere. C++11 pur și simplu extinde această sintaxă:
extern template class std :: vector < MyClass > ;Această expresie îi spune compilatorului să nu instanțieze șablonul în această unitate de traducere.
Aceste caracteristici au scopul de a face limbajul mai ușor de utilizat. Acestea vă permit să consolidați siguranța tipului, să minimizați duplicarea codului, să îngreunați utilizarea greșită a codului și așa mai departe.
Liste de inițializareConceptul de liste de inițializare a venit în C++ din C. Ideea este că o structură sau o matrice poate fi creată prin trecerea unei liste de argumente în aceeași ordine în care sunt definiți membrii structurii. Listele de inițializare sunt recursive, ceea ce le permite să fie utilizate pentru rețele de structuri și structuri care conțin structuri imbricate.
struct obiect { plutește mai întâi ; int secundă ; }; Obiect scalar = { 0,43f , 10 }; // un obiect, cu primul=0.43f și al doilea=10 Object anArray [] = {{ 13.4f , 3 }, { 43.28f , 29 }, { 5.934f , 17 }}; // matrice de trei obiecteListele de inițializare sunt foarte utile pentru listele statice și atunci când doriți să inițializați o structură la o anumită valoare. C++ conține, de asemenea, constructori, care pot conține munca generală de inițializare a obiectelor. Standardul C++ permite utilizarea listelor de inițializare pentru structuri și clase, cu condiția ca acestea să fie conforme cu definiția Plain Old Data (POD). Clasele non-POD nu pot folosi liste de inițializare pentru inițializare, inclusiv containere standard C++, cum ar fi vectorii.
C++11 are asociat conceptul de liste de inițializare și o clasă de șablon numită std::initializer_list . Acest lucru a permis constructorilor și altor funcții să primească liste de inițializare ca parametri. De exemplu:
clasa SequenceClass { public : SequenceClass ( std :: initializer_list < int > list ); };Această descriere vă permite să creați o SequenceClass dintr-o secvență de numere întregi, după cum urmează:
SequenceClass someVar = { 1 , 4 , 5 , 6 };Aceasta demonstrează cum funcționează un tip special de constructor pentru o listă de inițializare. Clasele care conțin astfel de constructori sunt tratate într-un mod special în timpul inițializării (vezi mai jos ).
Clasa std::initializer_list<> este definită în biblioteca standard C++11. Cu toate acestea, obiectele acestei clase pot fi create numai static de către compilatorul C++11 folosind sintaxa parantezei {}. Lista poate fi copiată după creare, totuși, aceasta va fi copiată după referință. Lista de inițializare este const: nici membrii săi, nici datele lor nu pot fi modificate după creare.
Deoarece std::initializer_list<> este un tip complet, poate fi folosit în mai mult decât doar constructori. Funcțiile obișnuite pot lua liste de inițializare tastate ca argument, de exemplu:
void FunctionName ( std :: initializer_list < float > list ); FunctionName ({ 1.0f , -3.45f , -0.4f });Containerele standard pot fi inițializate astfel:
std :: vector < std :: șir > v = { "xyzzy" , "plugh" , "abracadabra" }; std :: vector < std :: șir > v { "xyzzy" , "plugh" , "abracadabra" }; Inițializare genericăStandardul C++ conține o serie de probleme legate de inițializarea tipului. Există mai multe moduri de inițializare a tipurilor și nu toate duc la aceleași rezultate. De exemplu, sintaxa tradițională a unui constructor de inițializare ar putea arăta ca o declarație de funcție și trebuie să se acorde o atenție suplimentară pentru a împiedica compilatorul să o analizeze greșit. Numai tipurile agregate și tipurile POD pot fi inițializate cu inițializatoare agregate (de tipul SomeType var = {/*stuff*/};).
C++11 oferă o sintaxă care permite utilizarea unei singure forme de inițializare pentru toate tipurile de obiecte prin extinderea sintaxei listei de inițializare:
struct BasicStruct { int x ; y dublu ; }; struct AltStruct { AltStruct ( int x , dublu y ) : x_ ( x ), y_ ( y ) {} privat : int x_ ; dublu y_ ; }; BasicStruct var1 { 5 , 3.2 }; AltStruct var2 { 2 , 4.3 };Inițializarea var1 funcționează exact la fel ca inițializarea agregatelor, adică fiecare obiect va fi inițializat prin copierea valorii corespunzătoare din lista de inițializare. Dacă este necesar, se va aplica conversia implicită de tip. Dacă transformarea dorită nu există, codul sursă va fi considerat nevalid. În timpul inițializării lui var2 , constructorul va fi apelat.
Este posibil să scrieți cod astfel:
struct IdString { std :: stringname ; _ int identificator ; }; IdString GetString () { return { "SomeName" , 4 }; // Observați lipsa unor tipuri explicite }Inițializarea generică nu înlocuiește complet sintaxa de inițializare a constructorului. Dacă o clasă are un constructor care ia o listă de inițializare ( TypeName(initializer_list<SomeType>); ) ca argument, aceasta va avea prioritate față de alte opțiuni de creare a obiectelor. De exemplu, în C++11 std::vector conține un constructor care ia ca argument o listă de inițializare:
std :: vector < int > theVec { 4 };Acest cod va avea ca rezultat un apel de constructor care ia ca argument o listă de inițializare, mai degrabă decât un constructor cu un parametru care creează un container de dimensiunea dată. Pentru a apela acest constructor, utilizatorul va trebui să folosească sintaxa standard de invocare a constructorului.
Tip inferențăÎn C++ standard (și C), tipul unei variabile trebuie specificat în mod explicit. Cu toate acestea, odată cu apariția tipurilor de șabloane și a tehnicilor de metaprogramare a șablonului, tipul unor valori, în special a valorilor returnate de funcție, nu poate fi specificat cu ușurință. Acest lucru duce la dificultăți în stocarea datelor intermediare în variabile, uneori poate fi necesar să se cunoască structura internă a unei anumite biblioteci de metaprogramare.
C++11 oferă două moduri de a atenua aceste probleme. În primul rând, definiția unei variabile inițializabile explicit poate conține cuvântul cheie auto . Aceasta va avea ca rezultat crearea unei variabile de tipul valorii de inițializare:
auto someStrangeCallableType = std :: bind ( & SomeFunction , _2 , _1 , someObject ); auto otherVariable = 5 ;Tipul someStrangeCallableType va deveni tipul pe care implementarea concretă a funcției șablon îl returnează std::bindpentru argumentele date. Acest tip va fi determinat cu ușurință de către compilator în timpul analizei semantice, dar programatorul ar trebui să facă unele cercetări pentru a determina tipul.
Tipul otherVariable este , de asemenea, bine definit, dar poate fi definit la fel de ușor de programator. Acest tip este int , la fel ca o constantă întreagă.
În plus, cuvântul cheie decltype poate fi folosit pentru a determina tipul unei expresii în momentul compilării . De exemplu:
int someInt ; decltype ( someInt ) otherIntegerVariable = 5 ;Utilizarea decltype este cea mai utilă împreună cu auto , deoarece tipul unei variabile declarate auto este cunoscut doar de compilator. De asemenea, utilizarea decltype poate fi destul de utilă în expresiile care utilizează supraîncărcarea operatorului și specializarea șablonului.
autopoate fi folosit și pentru a reduce redundanța codului. De exemplu, în loc de:
pentru ( vector < int >:: const_iterator itr = myvec . cbegin ( ) ; itr != myvec . cend (); ++ itr )programatorul poate scrie:
pentru ( auto itr = myvec . cbegin (); itr != myvec . cend (); ++ itr )Diferența devine deosebit de vizibilă atunci când un programator folosește un număr mare de containere diferite, deși există încă o modalitate bună de a reduce codul redundant - folosind typedef.
Un tip marcat cu decltype poate fi diferit de tipul dedus cu auto .
#include <vector> int main () { const std :: vector < int > v ( 1 ); auto a = v [ 0 ]; // tip a - int decltype ( v [ 0 ]) b = 1 ; // tip b - const int& (valoare returnată // std::vector<int>::operator[](size_type) const) auto c = 0 ; // tip c - int auto d = c ; // tip d - int decltype ( c ) e ; // tip e - int, tip de entitate numită c decltype (( c )) f = c ; // tipul f este int& deoarece (c) este un lvalue decltype ( 0 ) g ; // tipul g este int deoarece 0 este o valoare r } For-buclă printr-o colecțieÎn C++ standard , repetarea elementelor unei colecții necesită mult cod . Unele limbaje, cum ar fi C# , au facilități care oferă o declarație „ foreach ” care parcurge automat elementele unei colecții de la început până la sfârșit. C++11 introduce o facilitate similară. Declarația for ușurează repetarea unei colecții de elemente:
int my_array [ 5 ] = { 1 , 2 , 3 , 4 , 5 }; pentru ( int & x : my_array ) { x *= 2 ; }Această formă de for, numită „range-based for” în engleză, va vizita fiecare element al colecției. Acest lucru se va aplica matricelor C , listelor de inițializare și oricăror alte tipuri care au funcții begin()și end()care returnează iteratoare . Toate containerele din biblioteca standard care au o pereche început/sfârșit vor funcționa cu o declarație for pe colecție.
Un astfel de ciclu va funcționa și, de exemplu, cu matrice asemănătoare C, deoarece C++11 introduce artificial pseudo-metodele necesare pentru ele (început, sfârșit și altele).
// parcurgerea bazată pe intervale a tabloului clasic int arr1 [] = { 1 , 2 , 3 }; for ( auto el : arr1 ); Funcții și expresii lambdaÎn C++ standard, de exemplu, când se utilizează algoritmii standard de bibliotecă C++ sort and find , este adesea nevoie să se definească funcții predicate în apropierea locului unde este apelat algoritmul. Există un singur mecanism în limbaj pentru aceasta: abilitatea de a defini o clasă de functor (trecerea unei instanțe a unei clase definite în interiorul unei funcții la algoritmi este interzisă (Meyers, STL efectiv)). Adesea, această metodă este prea redundantă și verbosă și îngreunează doar citirea codului. În plus, regulile standard C++ pentru clasele definite în funcții nu permit utilizarea acestora în șabloane și astfel le fac imposibil de utilizat.
Soluția evidentă a problemei a fost să permită definirea expresiilor lambda și a funcțiilor lambda în C++11. Funcția lambda este definită astfel:
[]( int x , int y ) { return x + y ; }Tipul de returnare al acestei funcții fără nume este calculat ca decltype(x+y) . Tipul returnat poate fi omis numai dacă funcția lambda are forma . Aceasta limitează dimensiunea funcției lambda la o singură expresie. return expression
Tipul de returnare poate fi specificat explicit, de exemplu:
[]( int x , int y ) -> int { int z = x + y ; întoarce z ; }Acest exemplu creează o variabilă temporară z pentru a stoca o valoare intermediară. Ca și în cazul funcțiilor normale, această valoare intermediară nu este păstrată între apeluri.
Tipul returnat poate fi complet omis dacă funcția nu returnează o valoare (adică tipul returnat este void )
De asemenea, este posibil să se utilizeze referințe la variabile definite în același domeniu ca și funcția lambda. Un set de astfel de variabile este de obicei numit închidere . Închiderile sunt definite și utilizate după cum urmează:
std :: vector < int > someList ; int total = 0 ; std :: for_each ( someList . begin (), someList . end (), [ & total ]( int x ) { total += x ; }); std :: cout << total ;Aceasta va afișa suma tuturor elementelor din listă. Variabila totală este stocată ca parte a închiderii funcției lambda. Deoarece se referă la variabila stivă total , își poate schimba valoarea.
Variabilele de închidere pentru variabilele locale pot fi definite și fără a utiliza simbolul de referință & , ceea ce înseamnă că funcția va copia valoarea. Acest lucru forțează utilizatorul să declare intenția de a se referi la sau de a copia o variabilă locală.
Pentru funcțiile lambda care sunt garantate să se execute în domeniul lor, este posibil să se utilizeze toate variabilele de stivă fără a fi nevoie de referințe explicite la acestea:
std :: vector < int > someList ; int total = 0 ; std :: for_each ( someList . begin (), someList . end (), [ & ]( int x ) { total += x ; });Metodele de implementare pot varia în interior, dar se așteaptă ca funcția lambda să stocheze un pointer către stiva funcției în care a fost creată, mai degrabă decât să opereze pe referințe individuale ale variabilelor stivei.
[&]Dacă se folosește în schimb [=], toate variabilele utilizate vor fi copiate, permițând ca funcția lambda să fie utilizată în afara domeniului de aplicare al variabilelor originale.
Metoda implicită de transfer poate fi completată și cu o listă de variabile individuale. De exemplu, dacă trebuie să treceți majoritatea variabilelor prin referință și una după valoare, puteți utiliza următoarea construcție:
int total = 0 ; valoare int = 5 ; [ & , valoare ]( int x ) { total += ( x * valoare ); } ( 1 ); //(1) apelează funcția lambda cu valoarea 1Acest lucru va face ca totalul să fie trecut prin referință și valoarea după valoare.
Dacă o funcție lambda este definită într-o metodă de clasă, este considerată prietenă a acelei clase. Astfel de funcții lambda pot folosi o referință la un obiect de tipul clasei și pot accesa câmpurile interne ale acestuia:
[]( SomeType * typePtr ) { typePtr -> SomePrivateMemberFunction (); }Acest lucru va funcționa numai dacă domeniul de aplicare al funcției lambda este o metodă de clasă SomeType .
Lucrarea cu acest pointer către obiectul cu care interacționează metoda curentă este implementată într-un mod special. Trebuie să fie marcat în mod explicit în funcția lambda:
[ this ]() { this -> SomePrivateMemberFunction (); }Utilizarea unui formular [&]sau [=]a unei funcții lambda face acest lucru disponibil automat.
Tipul de funcții lambda este dependent de implementare; numele acestui tip este disponibil numai pentru compilator. Dacă trebuie să treceți o funcție lambda ca parametru, aceasta trebuie să fie de tip șablon sau stocată folosind std::function . Cuvântul cheie auto vă permite să salvați o funcție lambda local:
auto myLambdaFunc = [ this ]() { this -> SomePrivateMemberFunction (); };În plus, dacă funcția nu acceptă argumente, atunci ()puteți omite:
auto myLambdaFunc = []{ std :: cout << "bună ziua" << std :: endl ; }; Sintaxa funcției alternativeUneori este nevoie de a implementa un șablon de funcție care ar avea ca rezultat o expresie care are același tip și aceeași categorie de valoare ca o altă expresie.
template < typename LHS , typename RHS > RETURN_TYPE AddingFunc ( const LHS & lhs , const RHS & rhs ) // ce ar trebui să fie RETURN_TYPE? { return lhs + rhs ; }Pentru ca expresia AddingFunc(x, y) să aibă același tip și aceeași categorie de valoare ca și expresia lhs + rhs atunci când sunt date argumente x și y , următoarea definiție ar putea fi utilizată în C++11:
template < typename LHS , typename RHS > decltype ( std :: declval < const LHS &> () + std :: declval < const RHS &> ()) AddingFunc ( const LHS & lhs , const RHS & rhs ) { return lhs + rhs ; }Această notație este oarecum greoaie și ar fi bine să puteți folosi lhs și rhs în loc de std::declval<const LHS &>() și, respectiv, std::declval<const RHS &>(). Cu toate acestea, în versiunea următoare
template < typename LHS , typename RHS > decltype ( lhs + rhs ) AddingFunc ( const LHS & lhs , const RHS & rhs ) // Nu este valabil în C++11 { return lhs + rhs ; }mai citibili de om, identificatorii lhs și rhs utilizați în operandul decltype nu pot denota opțiunile declarate mai târziu. Pentru a rezolva această problemă, C++11 introduce o nouă sintaxă pentru declararea funcțiilor cu un tip de returnare la sfârșit:
template < typename LHS , typename RHS > auto AddingFunc ( const LHS & lhs , const RHS & rhs ) -> decltype ( lhs + rhs ) { return lhs + rhs ; }Trebuie remarcat, totuși, că în implementarea AddingFunc mai generică de mai jos, noua sintaxă nu beneficiază de concizie:
template < typename LHS , typename RHS > Auto AddingFunc ( LHS && lhs , RHS && rhs ) -> decltype ( std :: forward < LHS > ( lhs ) + std :: forward < RHS > ( rhs )) { return std :: forward < LHS > ( lhs ) + std :: forward < RHS > ( rhs ); } template < typename LHS , typename RHS > Auto AddingFunc ( LHS && lhs , RHS && rhs ) -> decltype ( std :: declval < LHS > () + std :: declval < RHS > ()) // același efect ca și cu std::forward deasupra { return std :: forward < LHS > ( lhs ) + std :: forward < RHS > ( rhs ); } template < typename LHS , typename RHS > decltype ( std :: declval < LHS > () + std :: declval < RHS > ()) // același efect ca și punerea tipului la sfârșit AddingFunc ( LHS && lhs , RHS && rhs ) { return std :: forward < LHS > ( lhs ) + std :: forward < RHS > ( rhs ); }Noua sintaxă poate fi utilizată în declarații și declarații mai simple:
struct SomeStruct { auto FuncName ( int x , int y ) -> int ; }; auto SomeStruct :: FuncName ( int x , int y ) -> int { returnează x + y _ }Utilizarea cuvântului cheie „ ” autoîn acest caz înseamnă doar o indicare târzie a tipului de returnare și nu are legătură cu inferența sa automată.
Îmbunătățirea constructorilor de obiecteStandard C++ nu permite apelarea unui constructor de clasă de la un alt constructor din aceeași clasă; fiecare constructor trebuie să inițializeze complet toți membrii clasei sau să apeleze metodele clasei pentru a face acest lucru. Membrii non-const ai unei clase nu pot fi inițializați la locul în care acești membri sunt declarați.
C++11 scapă de aceste probleme.
Noul standard permite ca un constructor de clasă să fie apelat de la altul (așa-numita delegare). Acest lucru vă permite să scrieți constructori care folosesc comportamentul altor constructori fără a introduce cod duplicat.
Exemplu:
clasa SomeType { număr int ; public : SomeType ( int new_number ) : număr ( new_number ) {} SomeType () : SomeType ( 42 ) {} };Din exemplu, puteți vedea că constructorul SomeTypefără argumente apelează constructorul aceleiași clase cu un argument întreg pentru a inițializa variabila number. Un efect similar ar putea fi obținut prin specificarea unei valori inițiale de 42 pentru această variabilă drept la declararea acesteia.
clasa SomeType { număr int = 42 ; public : SomeType () {} explicit SomeType ( int new_number ) : număr ( new_number ) {} };Orice constructor de clasă se va inițializa numberla 42 dacă nu îi atribuie el însuși o valoare diferită.
Java , C# și D sunt exemple de limbaje care rezolvă și aceste probleme .
Trebuie remarcat faptul că, dacă în C++03 un obiect este considerat a fi creat în întregime atunci când constructorul său finalizează execuția, atunci în C++11, după ce a fost executat cel puțin un constructor de delegare, restul constructorilor vor lucra la un obiect complet construit. În ciuda acestui fapt, obiectele clasei derivate vor fi construite numai după ce toți constructorii claselor de bază au fost executați.
Înlocuirea explicită a funcțiilor virtuale și a finalitățiiEste posibil ca semnătura unei metode virtuale să fi fost schimbată în clasa de bază sau setată incorect în clasa derivată inițial. În astfel de cazuri, metoda dată din clasa derivată nu va înlocui metoda corespunzătoare din clasa de bază. Deci, dacă programatorul nu modifică corect semnătura metodei în toate clasele derivate, este posibil ca metoda să nu fie apelată corect în timpul execuției programului. De exemplu:
struct Base { virtual void some_func (); }; struct Derivat : Baza { void sone_func (); };Aici, numele unei funcții virtuale declarate într-o clasă derivată este scris greșit, astfel încât o astfel de funcție nu va suprascrie Base::some_funcși, prin urmare, nu va fi numită polimorf printr-un pointer sau referință la subobiectul de bază.
C++11 va adăuga capacitatea de a urmări aceste probleme în timpul compilării (mai degrabă decât în timpul executării). Pentru compatibilitatea anterioară, această caracteristică este opțională. Noua sintaxă este prezentată mai jos:
structura B { virtual void some_func (); virtual void f ( int ); virtual void g () const ; }; struct D1 : public B { void sone_func () override ; // eroare: nume de funcție invalid void f ( int ) override ; // OK: suprascrie aceeași funcție în clasa de bază virtual void f ( long ) override ; // eroare: tip de parametru nepotrivire virtual void f ( int ) const override ; // eroare: funcția cv-qualification nepotrivire virtual int f ( int ) override ; // eroare: tipul de returnare nepotrivire virtual void g () const final ; // OK: suprascrie aceeași funcție în clasa de bază virtual void g ( long ); // OK: nouă funcție virtuală }; structura D2 : D1 { virtual void g () const ; // eroare: încercați să înlocuiți funcția finală };Prezența unui specificator pentru o funcție virtuală finalînseamnă că înlocuirea sa ulterioară este imposibilă. De asemenea, o clasă definită cu specificatorul final nu poate fi utilizată ca clasă de bază:
struct F final { int x , y ; }; struct D : F // eroare: moștenirea din clasele finale nu este permisă { int z ; };Identificatorii overrideși finalau o semnificație specială numai atunci când sunt utilizați în anumite situații. În alte cazuri, aceștia pot fi utilizați ca identificatori normali (de exemplu, ca nume de variabilă sau funcție).
Constanta indicator nulDe la apariția lui C în 1972, constanta 0 a jucat rolul dublu de număr întreg și indicator nul. O modalitate de a face față acestei ambiguități inerente în limbajul C este macro-ul NULL, care efectuează de obicei substituția ((void*)0)sau 0. C++ diferă de C în acest sens, permițând doar utilizarea 0unui pointer nul ca constantă. Acest lucru duce la o interacțiune proastă cu supraîncărcarea funcției:
void foo ( char * ); void foo ( int );Dacă macro-ul NULLeste definit ca 0(ceea ce este comun în C++), linia foo(NULL);va avea ca rezultat un apel foo(int), nu foo(char *)așa cum ar putea sugera o privire rapidă asupra codului, ceea ce aproape sigur nu este ceea ce intenționa programatorul.
Una dintre noutățile C++11 este un nou cuvânt cheie pentru descrierea unei constante de indicator nul - nullptr. Această constantă este de tip std::nullptr_t, care poate fi implicit convertită în tipul oricărui pointer și comparată cu orice pointer. Conversia implicită într-un tip integral nu este permisă, cu excepția bool. Propunerea inițială a standardului nu permitea conversia implicită în boolean, dar grupul de redactare standard a permis astfel de conversii de dragul compatibilității cu tipurile de pointer convenționale. Formularea propusă a fost modificată în urma unui vot unanim din iunie 2008 [1] .
Pentru compatibilitatea inversă, o constantă 0poate fi folosită și ca indicator nul.
char * pc = nullptr ; // true int * pi = nullptr ; // true bool b = nullptr ; // dreapta. b=fals. int i = nullptr ; // eroare foo ( nullptr ); // apelează foo(char *), nu foo(int);Adesea, construcțiile în care indicatorul este garantat a fi gol sunt mai simple și mai sigure decât restul - așa că puteți supraîncărca cu . nullptr_t
clasa Sarcina utila ; clasa SmartPtr { SmartPtr () = implicit ; SmartPtr ( nullptr_t ) {} // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< explicit SmartPtr ( Payload * aData ) : fData ( aDate ) {} // copiază constructorii și op= omit ~ SmartPtr () { șterge fData ; } privat : Sarcina utilă * fData = nullptr ; } SmartPtr getPayload1 () { return nullptr ; } // Va fi apelată supraîncărcarea SmartPtr(nullptr_t). Enumerări puternic tastateÎn C++ standard, enumerările nu sunt de tip safe. De fapt, ele sunt reprezentate prin numere întregi, în ciuda faptului că tipurile de enumerare în sine sunt diferite unele de altele. Acest lucru permite să se facă comparații între două valori din enumerari diferite. Singura opțiune pe care C++03 o oferă pentru a proteja enumerarea este aceea de a nu converti implicit numerele întregi sau elemente ale unei enumerari în elemente ale altei enumerari. De asemenea, modul în care este reprezentat în memorie (tip întreg) este dependent de implementare și, prin urmare, nu este portabil. În cele din urmă, elementele de enumerare au un domeniu de aplicare comun, ceea ce face imposibilă crearea de elemente cu același nume în enumerari diferite.
C++11 oferă o clasificare specială a acestor enumerari, fără dezavantajele de mai sus. Pentru a descrie astfel de enumerari, se folosește o declarație enum class(poate fi folosită enum structși ca sinonim):
enumerare clasa enumerare { Val1 , Val2 , Val3 = 100 , Val4 , /* = 101 */ };O astfel de enumerare este de tip sigur. Elementele unei enumerari de clasă nu pot fi convertite implicit în numere întregi. În consecință, compararea cu numerele întregi este imposibilă (expresia Enumeration::Val4 == 101are ca rezultat o eroare de compilare).
Tipul de enumerare a clasei este acum independent de implementare. În mod implicit, ca și în cazul de mai sus, acest tip este int, dar în alte cazuri, tipul poate fi setat manual după cum urmează:
enum class Enum2 : unsigned int { Val1 , Val2 };Domeniul de aplicare al membrilor enumerare este determinat de domeniul de aplicare al numelui enumerare. Utilizarea numelor de elemente necesită specificarea numelui enumerarii clasei. Deci, de exemplu, valoarea Enum2::Val1este definită, dar valoarea Val1 nu este definită.
În plus, C++11 oferă posibilitatea de a defini în mod explicit domeniul și tipurile de bază pentru enumerări obișnuite:
enum Enum3 : lung nesemnat { Val1 = 1 , Val2 };În acest exemplu, numele elementelor enumerare sunt definite în spațiul enumerare (Enum3::Val1), dar pentru compatibilitate inversă, numele elementelor sunt disponibile și în domeniul comun.
De asemenea, în C++11 este posibil să se predeclare enumerari. În versiunile anterioare de C++, acest lucru nu a fost posibil deoarece dimensiunea unei enumerari depindea de elementele sale. Astfel de declarații pot fi utilizate numai atunci când dimensiunea enumerației este specificată (explicit sau implicit):
enumerare Enum1 ; // invalid pentru C++ și C++11; tipul de bază nu poate fi determinat enum Enum2 : unsigned int ; // adevărat pentru C++11, tipul de bază specificat explicit clasa enumerare Enum3 ; // adevărat pentru C++11, tipul de bază este int enum class Enum4 : unsigned int ; // adevărat pentru C++11. enum Enum2 : scurt nesemnat ; // nevalid pentru C++11 deoarece Enum2 a fost declarat anterior cu un alt tip de bază Paranteze unghiulareAnalizoarele standard C++ definesc întotdeauna combinația de caractere „>>” ca operator de schimbare la dreapta. Absența unui spațiu între parantezele unghiulare de închidere în parametrii șablonului (dacă sunt imbricați) este tratată ca o eroare de sintaxă.
C++11 îmbunătățește comportamentul parserului în acest caz, astfel încât mai multe paranteze în unghi drept vor fi interpretate ca liste de argumente șablon de închidere.
Comportamentul descris poate fi remediat în favoarea abordării vechi folosind paranteze.
template < clasa T > clasa Y { /* ... */ }; Y < X < 1 >> x3 ; // Corect, la fel ca "Y<X<1> > x3;". Y < X < 6 >> 1 >> x4 ; // Eroare de sintaxă. Trebuie să scrieți „Y<X<(6>>1)>> x4;”.După cum se arată mai sus, această modificare nu este pe deplin compatibilă cu standardul anterior.
Operatori de conversie explicităStandardul C++ oferă cuvântul cheie explicitca modificator pentru constructorii cu un parametru, astfel încât astfel de constructori să nu funcționeze ca constructori de conversie implicită. Cu toate acestea, acest lucru nu afectează în niciun fel operatorii de conversie actuali. De exemplu, o clasă de pointer inteligent poate conține operator bool()pentru a imita un pointer normal. Un astfel de operator poate fi apelat, de exemplu, astfel: if(smart_ptr_variable)(ramura este executată dacă pointerul este non-null). Problema este că un astfel de operator nu protejează împotriva altor conversii neașteptate. Deoarece tipul booleste declarat ca tip aritmetic în C++, este posibilă conversia implicită în orice tip întreg sau chiar într-un tip în virgulă mobilă, care la rândul său poate duce la operații matematice neașteptate.
În C++11, cuvântul cheie explicitse aplică și operatorilor de conversie. Ca și constructorii, protejează împotriva conversiilor implicite neașteptate. Cu toate acestea, situațiile în care limbajul așteaptă contextual un tip boolean (de exemplu, în expresii condiționate, bucle și operanzi de operator logic) sunt considerate conversii explicite, iar operatorul de conversie bool explicit este invocat direct.