C++11

Versiunea actuală a paginii nu a fost încă examinată de colaboratori experimentați și poate diferi semnificativ de versiunea revizuită pe 15 septembrie 2020; verificările necesită 24 de modificări .

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.

Modificări propuse la standard

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 .

Extinderea limbajului de bază

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ă.

Îmbunătățirea performanței

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 mutare

Conform 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 generice

C++ 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.


int GiveFive () { return 5 ;} int some_value [ Dă cinci () + 7 ]; // creează o matrice de 12 numere întregi; interzis în C++

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++11

Acest 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:

  1. o astfel de funcție trebuie să returneze o valoare;
  2. corpul funcției trebuie să fie de forma expresiei returnate ;
  3. expresia trebuie să fie compusă din constante și/sau apeluri la alte funcții constexpr ;
  4. o funcție notată cu constexpr nu poate fi utilizată până când este definită în unitatea de compilare curentă.

Î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:

  1. conține un constructor implicit trivial,
  2. nu conține constructori de copie non-triviali,
  3. nu conține constructori de mutare non-triviali,
  4. nu conține operatori non-triviali de atribuire a copiei,
  5. nu conține operatori non-triviali de atribuire a mișcării,
  6. conţine un destructor banal.

O clasă cu plasare standard este o clasă care:

  1. nu conține membri de date non-statici ai unui tip de clasă plasat personalizat (sau o matrice de elemente de acest tip) sau un tip de referință,
  2. nu conține funcții virtuale,
  3. nu conține clase de bază virtuale,
  4. are același tip de accesibilitate ( public, private, protected) pentru toți membrii de date nestatice,
  5. nu are clase de bază cu plasare non-standard,
  6. nu este o clasă care conține simultan membri de date non-statici moșteniți și nemoșteniți sau care conține membri de date non-statici moșteniți de la mai multe clase de bază simultan;
  7. nu are clase de bază de același tip ca primul membru de date non-static (dacă există).

Accelerează compilarea

Șabloane externe

Î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.

Utilizabilitate îmbunătățită

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țializare

Conceptul 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 obiecte

Listele 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 1

Acest 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 alternative

Uneori 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 obiecte

Standard 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ății

Este 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 nul

De 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 unghiulare

Analizoarele 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.

Template typedef

În C++ standard, un cuvânt cheie typedefpoate fi folosit doar ca definiție de sinonim pentru alt tip, inclusiv ca sinonim pentru specificația unui șablon cu toți parametrii săi specificați. Dar nu este posibil să creați un sinonim șablon. De exemplu:

template < typename First , typename Second , int al treilea > clasa SomeType ; template < typenameSecond > _ typedef SomeType < OtherType , Second , 5 > TypedefName ; // Nu este posibil în C++

Aceasta nu se va compila.

C++11 a adăugat această capacitate cu următoarea sintaxă:

template < typename First , typename Second , int al treilea > clasa SomeType ; template < typenameSecond > _ folosind TypedefName = SomeType < OtherType , Second , 5 > ;

În C++11, directiva usingpoate fi folosită și pentru a alia un tip de date.

typedef void ( * OtherType )( double ); // Stil vechi folosind OtherType = void ( * )( double ); // Sintaxă nouă Eliminarea restricțiilor de la unire

În standardele C++ anterioare, există o serie de restricții privind utilizarea membrilor tipurilor de clasă în cadrul uniunilor. În special, uniunile nu pot conține obiecte cu un constructor non-trivial. C++11 elimină unele dintre aceste restricții. [2]

Iată un exemplu simplu de unire care este permisă în C++11:

//pentru plasare nouă #include <nou> structPoint { _ Punct () {} Punct ( int x , int y ) : x_ ( x ), y_ ( y ) {} int x_ , y_ ; }; unire U { int z ; w dublu ; Punctul p ; // Nu este adevărat pentru C++03 deoarece Point are un constructor non-trivial. Cu toate acestea, codul funcționează corect în C++11. U () { nou ( & p ) Punct (); } // Nu sunt definite metode non-triviale pentru unire. // Dacă este necesar, acestea pot fi eliminate pentru ca definiția manuală să funcționeze };

Modificările nu afectează codul existent, deoarece ele doar slăbesc restricțiile existente.

Extinderea funcționalității

Această secțiune descrie funcții noi care anterior nu erau disponibile sau necesitau biblioteci speciale non-portabile.

Șabloane de argumente variabile

Înainte de C++11, șabloanele (de clase sau funcții) puteau lua doar un număr stabilit de argumente, definit atunci când șablonul a fost declarat inițial. C++11 vă permite să definiți șabloane cu un număr variabil de argumente de orice tip.

template < typename ... Values ​​​​> class tuple ;

De exemplu, clasa șablonului tuple ( tuple ) acceptă orice număr de nume de tip ca parametri de șablon:

class tuple < int , std :: vector < int > , std :: map < std :: string , std :: vector < int >>> some_instance_name ;

Argumentele pot lipsi, așa că și opțiunea class tuple<> some_instance_nameva funcționa.

Pentru a preveni instanțiarea șablonului fără argumente, se poate folosi următoarea definiție:

template < typename First , typename ... Rest > class tuple ;

Șabloanele cu argumente variabile sunt, de asemenea, aplicabile funcțiilor, permițându-le să fie utilizate în variante de tip sigur ale funcțiilor variadice (cum ar fi printf) și pentru manipularea obiectelor non-triviale.

template < typename ... Params > void printf ( const std :: string & str_format , Params ... parametri );

Operatorul ... joacă două roluri aici. În stânga Params, un operator anunță necesitatea de a împacheta parametrii. Utilizarea parametrilor împachetati permite ca 0 sau mai multe argumente să fie asociate unui șablon. Parametrii împachetati pot fi utilizați pentru mai mult decât pentru transmiterea numelor de tip. Operatorul ... din dreapta, la rândul său, despachetează parametrii în argumente separate (a se vedea args...corpul funcției în exemplul de mai jos).

De asemenea, este posibil să se utilizeze recursiv șabloane cu un număr variabil de argumente. Un exemplu ar fi înlocuirea tip-safe pentru printf :

void printf ( const char * s ) { în timp ce ( * s ) { dacă ( * s == '%' && * ( ++ s ) != '%' ) throw std :: runtime_error ( "șir de format nevalid: argumente lipsă" ); std :: cout << * s ++ ; } } template < typename T , typename ... Args > void printf ( const char * s , T value , Args ... args ) { în timp ce ( * s ) { dacă ( * s == '%' && * ( ++ s ) != '%' ) { std :: cout << valoare ; ++ s ; printf ( s , args ...); // continuă procesarea argumentelor chiar dacă *s == 0 return ; } std :: cout << * s ++ ; } throw std :: logic_error ( „argumente suplimentare furnizate pentru printf” ); }

Acest model este recursiv. Rețineți că funcția printf apelează rezultatele instanțierii sau funcția de bază printf dacă args... este goală.

Nu există o modalitate ușoară de a ocoli parametrii într-un șablon variadic. În ciuda acestui fapt, utilizarea argumentului operator de despachetare ocolește această problemă.

De exemplu, o clasă ar putea fi definită astfel:

template < typename ... BaseClasses > class ClassName : public BaseClasses ... { public : ClassName ( BaseClasses && ... baze_clases ) : BaseClasses ( bază_clases )... {} };

Operatorul de despachetare va duplica toate tipurile de clase părinte ClassNameîn așa fel încât clasa să fie moștenită de la toate tipurile specificate în parametrii șablonului. În plus, constructorul trebuie să accepte o referință la toate clasele de bază, astfel încât fiecare clasă de bază părinte să fie inițializată ClassName.

Parametrii șablonului pot fi redirecționați. Combinat cu referințele rvalue (vezi mai sus), puteți redirecționa:

template < typename TypeToConstruct > struct SharedPtrAllocator { template < typename ... Args > std :: shared_ptr < TypeToConstruct > construct_with_shared_ptr ( Args && ... params ) { return std :: shared_ptr < TypeToConstruct > ( nou TypeToConstruct ( std :: forward < Args > ( parametri )...)); }; };

Acest cod despachetează lista de argumente în constructorul TypeToConstruct. Sintaxa std::forward<Args>(params)vă permite să redirecționați în mod absolut transparent argumentele către constructor, indiferent de natura lor rvalue. Funcția include automat indicatorii std::shared_ptrpentru a oferi protecție împotriva scurgerilor de memorie.

De asemenea, este posibil să specificați numărul de argumente împachetate după cum urmează:

template < typename ... Args > struct SomeStruct { static const int size = sizeof ...( Args ); };

Aici SomeStruct<Type1, Type2>::sizeeste egal cu 2 și SomeStruct<>::sizeegal cu 0.

Literale noi de șir

C++03 a oferit două tipuri de literali șir. Primul tip, un șir cu ghilimele duble, este o matrice terminată în nul de tip const char. Al doilea tip, definit ca L"", este o matrice terminată în nul de tip const wchar_t, unde wchar_teste un caracter larg de dimensiuni și semantică nedeterminate. Niciunul dintre tipurile de literale nu este destinat să accepte literali de șir UTF-8 , UTF-16 sau orice alt tip de codificare Unicode

Definiția tipului chara fost modificată pentru a spune în mod explicit că are cel puțin dimensiunea necesară pentru a stoca o codificare UTF-8 pe opt biți și suficient de mare pentru a conține orice caracter din setul de caractere de rulare. Mai devreme în standard, acest tip a fost definit ca un singur caracter, mai târziu, urmând standardul limbajului C, a devenit garantat să ocupe cel puțin 8 biți.

Există trei codificări Unicode care sunt acceptate în standardul C++11: UTF-8 , UTF-16 și UTF-32 . Pe lângă modificările de mai sus la tipul de caracter încorporat char, C++11 adaugă două tipuri de caractere noi: char16_tși char32_t. Sunt concepute pentru a stoca caractere UTF-16, respectiv UTF-32.

Următoarele arată cum să creați literale de șir pentru fiecare dintre aceste codificări:

u8 „Sunt un șir UTF-8”. u „Acesta este un șir UTF-16”. U „Acesta este un șir UTF-32”.

Tipul primului rând este normal const char[]. Tipul celei de-a doua linii este const char16_t[]. Tipul celei de-a treia linii este const char32_t[].

Când construiți literale șir în standardul Unicode, este adesea util să introduceți codul Unicode direct în șir. C++11 oferă următoarea sintaxă pentru aceasta:

u8 „Acesta este un caracter Unicode: \u2018 ”. u „Acesta este un caracter Unicode mai mare: \u2018 ”. U „Acesta este un caracter Unicode: \U00002018 ”.

Numărul de după \utrebuie să fie hexazecimal; nu este nevoie să folosiți prefixul 0x. Identificatorul \uînseamnă un cod Unicode pe 16 biți; pentru a introduce un cod de \U32 de biți, se folosește și un număr hexazecimal de 32 de biți. Pot fi introduse numai coduri Unicode valide. De exemplu, codurile din intervalul U+D800-U+DFFF nu sunt permise deoarece sunt rezervate perechilor surogat UTF-16.

De asemenea, uneori este util să evitați evadarea manuală a șirurilor de caractere, mai ales când utilizați literale de fișier XML , limbaje de scriptare sau expresii regulate. În aceste scopuri, C++11 acceptă literale șiruri „brute”:

R"(Datele șir \ Lucruri " )" R"delimitator(The String Data \ Stuff " )delimiter"

În primul caz, totul între "(și )"face parte din șir. Personajele "și \nu trebuie să fie scăpate. În al doilea caz , "delimiter(începe un șir și se termină doar când ajunge la )delimiter". Șirul delimiterpoate fi orice șir de până la 16 caractere, inclusiv șirul gol. Acest șir nu poate conține spații, caractere de control, „ („, „ )„ sau caracterul „ \„. Folosirea acestui șir delimitator permite ca caracterul „ )” să fie utilizat în literalele șiruri brute. De exemplu, R"delimiter((a-z))delimiter"este echivalent cu "(a-z)"[3] .

Literale de șir „brute” pot fi combinate cu un set literal extins (prefix L"") sau cu orice prefix literal Unicode.

LR"(Literal șir larg brut \t (fără filă))" u8R „XXX(Sunt un șir „UTF-8 brut”.)XXX” uR"*(Acesta este un șir „cru UTF-16”.)*” UR"(Acesta este un șir „cru UTF-32”.)” Litere personalizate

Literale personalizate sunt implementate folosind supraîncărcarea operatorului operator"". Literalele pot fi calificative inline sau constexpr . Este de dorit ca literalul să înceapă cu un caracter de subliniere, deoarece poate exista un conflict cu standardele viitoare. De exemplu, literalul i aparține deja numerelor complexe din std::complex.

Literale pot lua doar unul dintre următoarele tipuri: const char * , unsigned long long int , long double , char , wchar_t , char16_t , char32_t. Este suficient să supraîncărcați literalul numai pentru tipul const char * . Dacă nu se găsește niciun candidat potrivit, atunci va fi apelat un operator de acest tip. Un exemplu de conversie a milelor în kilometri:

constexpr int operator "" _mi ( unsigned long long int i ) { return 1,6 * i ;}

Literale șiruri iau un al doilea argument std::size_tși unul dintre primul: const char * , const wchar_t *, const char16_t * , const char32_t *. Literale șiruri se aplică intrărilor cuprinse între ghilimele duble.

Model de memorie multithreaded

C++11 standardizează suportul pentru programarea cu mai multe fire. Există două părți implicate: un model de memorie care permite mai multor fire de execuție să coexiste într-un program și o bibliotecă care acceptă comunicarea între fire.

Modelul de memorie definește modul în care mai multe fire de execuție pot accesa aceeași locație de memorie și definește când modificările făcute de un fir de execuție devin vizibile pentru alte fire.

Stocare cu fire Implicit explicit și eliminarea metodelor speciale

Specificatori defaultși deletepot fi specificate în locul corpului metodei.

clasa Foo { public : foo () = implicit ; Foo ( int x ) { /* ... */ } };

Specificatorul defaultînseamnă implementarea implicită și poate fi aplicat numai funcțiilor membre speciale:

  • constructor implicit;
  • constructor de copiere;
  • muta constructor;
  • operator de atribuire;
  • operator de mutare;
  • distrugător.

Specificatorul deletemarchează acele metode cu care nu se poate lucra. Anterior, trebuia să declarați astfel de constructori în domeniul privat al clasei.

clasa Foo { public : foo () = implicit ; Foo ( const Foo & ) = şterge ; void bar ( int ) = delete ; bară goală ( dublă ) {} }; // ... Foo obj ; obj . bară ( 5 ); // eroare! obj . bar ( 5,42 ); // O.K Tastați long long int

Tipul întreg long long inteste specificat în C99 și este utilizat pe scară largă de facto în C++. Acum este inclusă oficial în standard.

Diagnosticare statică

C++11 are două mecanisme statice de diagnosticare:

  • Cuvântul cheie static_assertaruncă o eroare de compilare dacă expresia dintre paranteze este falsă.
  • O bibliotecă type_traitscare conține șabloane care furnizează informații de tip în momentul compilării.
#include <type_traits> șablon < classT > _ void run ( T * aData , size_t n ) { static_assert ( std :: is_pod < T >:: value , „Tipul T trebuie să fie simplu.” ); ... } Lucrul cu dimensiunea membrilor de date din clase fără a crea un obiect

C++03 a permis ca operatorul sizeofsă fie utilizat pe tipuri și obiecte simple. Dar următorul construct a fost invalid:

struct SomeType { membru OtherType ; }; sizeof ( SomeType :: member ); //Nu funcționează în C++03, dar adevărat în C++11.

Rezultatul acestui apel ar trebui să fie o dimensiune OtherType. C++03 nu acceptă un astfel de apel și acest cod nu se va compila. C++11 permite astfel de construcții.

Controlul alinierii obiectelor și solicitările de aliniere

C++11 vă permite să aliniați variabile folosind operatorii alignofși alignas.

alignofia un tip și returnează numărul de octeți cu care obiectul poate fi mutat. De exemplu struct X { int n; char c; };, pentru 8 octeți, alignofva returna valoarea 4. Pentru legături, returnează valoarea pentru tipul de legătură; pentru matrice, valoarea pentru elementul de matrice

alignascontrolează alinierea unui obiect în memorie. De exemplu, puteți specifica că o matrice de caractere trebuie aliniată corect pentru a stoca tipul float:

aliñas ( float ) unsigned char c [ sizeof ( float )] Permiterea implementărilor cu un colector de gunoi Atribute

Modificări ale bibliotecii standard C++

Modificări ale componentelor existente

  • Când este lipit în, std::setprogramatorul știe uneori în ce poziție va ajunge noul element. Pentru aceasta, se folosește un parametru opțional - „hint”; dacă presupunerea este corectă, timpul estimat va fi o constantă amortizată, nu O(log n) . Sensul „hint” în C++11 a fost schimbat: mai devreme însemna elementul dinaintea celui actual, ceea ce nu este în întregime corect: nu este clar ce să faci dacă inserția este în prima poziție. Acum acesta este elementul după cel actual.
  • A fost scris un șablon convenabil care apelează constructorii fără alocare de memorie - std::allocator_traits<>::construct(). La toate containerele a fost adăugată o metodă emplacecare creează un obiect pe loc.
  • S-au adăugat noi caracteristici ale limbajului C++11.
  • Metode adăugate cbeginși cendgarantat pentru a crea iteratoare const. Convenabil pentru metaprogramare, pentru setarea tipurilor prin auto.
  • În containerele care încep memoria cu o marjă, a apărut o funcție shrink_to_fit.
  • B std::listpune limite mai stricte pentru ceea ce se face în O ( n ) și ceea ce se face în timp constant.
  • S-a adăugat std::vectoracces direct la memorie prin data().
  • Interziceți mai multor std::stringsă se refere la aceeași memorie. Datorită acestui fapt, a apărut accesul direct prin front(), care este convenabil, de exemplu, pentru interacțiunea dintre șir și WinAPI .

Controlul fluxului

În timp ce limbajul C++03 oferă un model de memorie care acceptă multithreading, suportul principal pentru utilizarea efectivă multithreading este oferit de biblioteca standard C++11.

Este furnizată o clasă de fire de execuție ( std::thread) care acceptă un obiect funcție (și o listă opțională de argumente care trebuie transmise acestuia) pentru a rula pe un fir nou. Puteți forța un fir de execuție să se oprească înainte ca un alt fir de execuție să se termine, oferind suport pentru gruparea firelor de execuție printr-o funcție membru std::thread::join(). Dacă este posibil, accesul la mânerul nativ al firului de execuție este oferit pentru operațiuni specifice platformei prin intermediul funcției de membru std::thread::native_handle().

Pentru sincronizarea între fire, mutexurile adecvate ( std::mutex, std::recursive_mutexetc.) și variabilele de condiție ( std::condition_variableși std::condition_variable_any) sunt adăugate la bibliotecă. Acestea sunt disponibile prin blocări de inițializare a resurselor (RAII) ( std::lock_guardși std::unique_lock) și prin algoritmi de blocare pentru ușurință în utilizare.

Munca de înaltă performanță la nivel scăzut necesită uneori comunicarea între fire, fără suprasolicitarea mutexurilor. Acest lucru se face folosind operații atomice pe locații de memorie. Opțional, aceștia pot specifica limitele minime de vizibilitate a memoriei necesare pentru operațiune. Barierele explicite ale memoriei pot fi, de asemenea, folosite în acest scop.

Biblioteca de fire C++11 include, de asemenea, futures și promisiuni pentru transmiterea rezultatelor asincrone între fire și o clasă std::packaged_taskpentru împachetarea unui apel de funcție care poate genera un astfel de rezultat asincron. Propunerea de futures a fost criticată deoarece îi lipsește o modalitate de a combina futures și de a verifica îndeplinirea unei singure promisiuni într-un set de promisiuni.

Facilități suplimentare de threading de nivel înalt, cum ar fi pool-uri de fire, au fost plasate într-o viitoare carte albă C++. Ele nu fac parte din C++11, dar eventuala lor implementare este de așteptat să fie construită în întregime pe deasupra caracteristicilor bibliotecii de threading.

Noua funcție std::asyncoferă o modalitate convenabilă de a rula sarcini și de a lega rezultatul executării lor la un obiect al std::future. Utilizatorul poate alege dacă jobul va rula asincron pe un fir separat sau sincron pe firul curent care așteaptă valoarea.

Tabele hash

std::hash_setși std::hash_mapau fost mult timp o extensie STL non-standard, de fapt implementată în majoritatea compilatoarelor. În C++11 au devenit standard, sub numele std::unordered_setși std::unordered_map. Deși sunt de fapt tabele hash și standardul nu lasă prea mult spațiu de mișcare, denumirile sunt date în stil C++: nu „cum sunt implementate”, ci „ce sunt”.

Expresii regulate

Noua bibliotecă, declarată în fișierul antet <regex>, conține mai multe clase noi:

  • Expresiile regulate sunt reprezentate ca instanțe ale std::regex;
  • rezultatele căutării sunt reprezentate ca instanțe șablon std::match_results.

Funcția std::regex_searcheste folosită pentru căutare, pentru operațiunea „găsiți și înlocuiți” funcția este utilizată std::regex_replace. Funcția va returna un șir după efectuarea înlocuirii. Algoritmii std::regex_searchși std::regex_replaceiau o expresie regulată și un șir ca intrare și returnează rezultatele găsite ca o instanță de std::match_results.

Exemplu de utilizare std::match_results:

const char * reg_esp = "[ ,. \\ t \\ n;:]" ; // Lista de caractere separatoare. // același lucru se poate face folosind șiruri „raw”: // const char *reg_esp = R"([ ,.\t\n;:])"; std :: regex rgx ( reg_esp ); // „regex” este o instanță a clasei de șablon // „basic_regex” cu parametrul de șablon „char”. std :: cmatch match ; // „cmatch” este o instanță a clasei de șablon // „match_results” cu parametrul de șablon „const char *”. const char * target = "Universitatea nevăzută - Ankh-Morpork" ; // Remediază toate cuvintele din șirul „țintă” separate prin caractere de „reg_esp”. if ( std :: regex_search ( țintă , potrivire , rgx ) ) { // Dacă cuvintele separate prin caracterele date sunt prezente în șir. const size_t n = potrivire . dimensiune (); pentru ( dimensiunea_t a = 0 ; a < n ; a ++ ) { std :: șir str ( potriviți [ a ]. primul , potriviți [ a ]. al doilea ); std :: cout << str << " \n " ; } }

Rețineți că barele oblice inverse duble sunt necesare deoarece C++ folosește barele oblice inverse pentru a scăpa de caractere. Puteți folosi „șiruri brute” - o altă inovație a standardului C++11.

Biblioteca <regex>nu necesită nicio modificare a fișierelor antet existente și nici instalarea unor extensii de limbă suplimentare.


Clase de generare de numere aleatoare extensibile

Biblioteca standard C a permis generarea de numere pseudoaleatoare folosind rand. Cu toate acestea, comportamentul său poate varia în funcție de implementare.

Această funcționalitate este împărțită în două părți: motorul generator, care conține starea curentă a generatorului de numere aleatoare și produce numere pseudoaleatoare și distribuția, care determină intervalul și distribuția matematică a rezultatului. Combinația acestor două obiecte creează un generator de numere aleatorii.

Motoare generatoare:

Distribuții:

Exemplu:

#include <aleatoriu> #include <functional> std :: uniform_int_distribution < int > distribuție ( 0 , 99 ); std :: mt19937engine ; _ // Mersenne vortex MT19937 auto generator = std :: bind ( distribuție , motor ); int random = generator (); // Obține un număr aleator între 0 și 99. int random2 = distribuție ( motor ); // Obține un număr aleator folosind motorul și distribuția direct.



Caracteristicile planificate nu sunt incluse în standard

Module Volumul imens de fișiere de antet a dus la o creștere pătratică a timpului de compilare: atât cantitatea de cod, cât și numărul de module dintr-o singură unitate de compilare cresc. Modulele ar trebui să ofere un mecanism similar cu fișierele Delphi DCU sau cu fișierele de clasă Java .

Caracteristici eliminate sau depreciate

Vezi și

Note

  1. Herb Sutter , Avem un standard internațional: C++0x este aprobat în unanimitate Arhivat 11 decembrie 2018 la Wayback Machine
  2. Scott Meyers , Summary of C++11 Feature Availability in gcc and MSVC Arhivat 26 octombrie 2011 la Wayback Machine , 16 august 2011
  3. ISO , ISO/IEC 14882:2011 Arhivat 29 ianuarie 2013 la Wayback Machine
  4. Numele C++0x definit în versiunea finală a N3290 Arhivat 20 iunie 2010 la Wayback Machine
  5. Stroustrup, Bjorn  - C++0x - următorul standard ISO C++ Arhivat 11 mai 2011 la Wayback Machine
  6. C++ Standards Committee Papers . Consultat la 24 februarie 2008. Arhivat din original pe 18 martie 2010.
  7. Sursa C++ Bjarne Stroustrup ( 2 ianuarie 2006 ) O scurtă privire asupra C++0x . (Engleză)

Documente ale Comitetului de standarde C++

  •   Doc nr. 1401: Jan Kristoffersen (21 octombrie 2002)Operații atomice cu medii cu mai multe fire
  •   Doc nr. 1402: Doug Gregor (22 octombrie 2002)O propunere de a adăuga un înveliș de obiecte cu funcție polimorfă la biblioteca standard
  •   Doc nr. 1403: Doug Gregor (8 noiembrie 2002)Propunere pentru adăugarea de tipuri de tuplu în biblioteca standard
  •   Doc nr. 1424: John Maddock (3 martie 2003)O propunere de a adăuga trăsături de tip la biblioteca standard
  •   Doc nr. 1429: John Maddock (3 martie 2003)O propunere de adăugare a expresiei regulate la biblioteca standard
  •   Doc nr. 1449: B. Stroustrup, G. Dos Reis, Mat Marcus, Walter E. Brown, Herb Sutter (7 aprilie 2003)Propunere de a adăuga aliasuri de șablon la C++
  •   Doc nr. 1450: P. Dimov, B. Dawes, G. Colvin (27 martie 2003)O propunere de a adăuga indicatori inteligenti cu scop general la Raportul tehnic al bibliotecii (Reviziunea 1)
  •   Doc nr. 1452: Jens Maurer (10 aprilie 2003)O propunere de a adăuga o facilitate de numere aleatoare extensibile la biblioteca standard (reviziunea 2)
  •   Doc nr. 1453: D. Gregor, P. Dimov (9 aprilie 2003)O propunere de adăugare a unui wrapper de referință la biblioteca standard (reviziunea 1)
  •   Doc nr. 1454: Douglas Gregor, P. Dimov (9 aprilie 2003)O metodă uniformă pentru calcularea tipurilor de returnare a obiectelor funcție (reviziunea 1)
  •   Doc nr. 1456: Matthew Austern (9 aprilie 2003)O propunere de a adăuga tabele Hash la Biblioteca standard (reviziunea 4)
  •   Doc nr. 1471: Daveed Vandevoorde (18 aprilie 2003)Metaprogramare reflectivă în C++
  •   Doc nr. 1676: Bronek Kozicki (9 septembrie 2004)Operator de atribuire a copiei supraîncărcate fără membri
  •   Doc nr. 1704: Douglas Gregor, Jaakko Järvi, Gary Powell (10 septembrie 2004)Șabloane variadice: explorarea spațiului de proiectare
  •   Doc nr. 1705: J. Järvi, B. Stroustrup, D. Gregor, J. Siek, G. Dos Reis (12 septembrie 2004)Decltype (și auto)
  •   Doc nr. 1717: Francis Glassborow, Lois Goldthwaite (5 noiembrie 2004)definiții explicite ale clasei și implicite
  •   Doc nr. 1719: Herb Sutter, David E. Miller (21 octombrie 2004)Enumerări puternic tipizate (reviziunea 1)
  •   Doc nr. 1720: R. Klarer, J. Maddock, B. Dawes, H. Hinnant (20 octombrie 2004)Propunere de a adăuga afirmații statice la limbajul de bază (Reviziunea 3)
  •   Doc nr. 1757: Daveed Vandevoorde (14 ianuarie 2005)Paranteze cu unghi drept (Reviziunea 2)
  •   Doc nr. 1811: J. Stephen Adamczyk (29 aprilie 2005)Adăugarea tipului lung lung la C++ (Reviziunea 3)
  •   Doc nr. 1815: Lawrence Crowl (2 mai 2005)ISO C++ Plan strategic pentru multithreading
  •   Doc nr. 1827: Chris Uzdavinis, Alisdair Meredith (29 august 2005)O sintaxă de anulare explicită pentru C++
  •   Doc nr. 1834: Detlef Vollmann (24 iunie 2005)O pledoarie pentru suport rezonabil de procesare paralelă în C++
  •   Doc nr. 1836: ISO/IEC DTR 19768 (24 iunie 2005)Proiect de raport tehnic privind extensiile bibliotecii C++
  •   Doc nr. 1886: Gabriel Dos Reis, Bjarne Stroustrup (20 octombrie 2005)Specificarea conceptelor C++
  •   Doc nr. 1891: Walter E. Brown (18 octombrie 2005)Progresul către opace Typedefs pentru C++0X
  •   Doc nr. 1898: Michel Michaud, Michael Wong (6 octombrie 2004)Redirecționare și constructori moșteniți
  •   Doc nr. 1919: Bjarne Stroustrup, Gabriel Dos Reis (11 decembrie 2005)Liste de inițializatori
  •   Doc nr. 1968: V Samko J Willcock, J Järvi, D Gregor, A Lumsdaine (26 februarie 2006)Expresii lambda și închideri pentru C++
  •   Doc nr. 1986: Herb Sutter, Francis Glassborow (6 aprilie 2006)Delegarea constructorilor (reviziunea 3)
  •   Doc nr. 2016: Hans Boehm, Nick Maclaren (21 aprilie 2002)Ar trebui ca volatilele să dobândească atomicitatea și semantica vizibilității firelor?
  •   Doc nr. 2142: ISO/IEC DTR 19768 (12 ianuarie 2007)Starea evoluției C++ (între întâlnirile din 2007 din Portland și Oxford)
  •   Doc nr. 2228: ISO/IEC DTR 19768 (3 mai 2007)Starea evoluției C++ (Întâlniri de la Oxford 2007)
  •   Doc nr. 2258: G. Dos Reis și B. StroustrupTemplates Aliases
  •   Doc nr. 2280: Lawrence Crowl (2 mai 2007)Stocare locală de
  •   Doc nr. 2291: ISO/IEC DTR 19768 (25 iunie 2007)Starea evoluției C++ (Întâlniri din Toronto 2007)
  •   Doc nr. 2336: ISO/IEC DTR 19768 (29 iulie 2007)Starea evoluției C++ (Întâlniri din Toronto 2007)
  •   Doc nr. 2389: ISO/IEC DTR 19768 (7 august 2007)Starea evoluției C++ (întâlnirile pre-Kona 2007)
  •   Doc nr. 2431: SC22/WG21/N2431 = J16/07-0301 (2 octombrie 2007),Un nume pentru indicatorul nul: nullptr
  •   Doc nr. 2432: ISO/IEC DTR 19768 (23 octombrie 2007)Starea evoluției C++ (întâlnirea post-Kona 2007)
  •   Doc nr. 2437: Lois Goldthwaite (5 octombrie 2007)Operatori de conversie explicită
  •   Doc nr. 2461: ISO/IEC DTR 19768 (22 octombrie 2007)Proiect de lucru, Standard pentru programare Limbajul C++
  •   Doc nr. 2507: ISO/IEC DTR 19768 (4 februarie 2008)Starea evoluției C++ (întâlnirea pre-Bellevue 2008)
  •   Doc nr. 2544: Alan Talbot, Lois Goldthwaite, Lawrence Crowl, Jens Maurer (29 februarie 2008) Sindicațiifără restricții
  •   Doc nr. 2565: ISO/IEC DTR 19768 (7 martie 2008)Starea evoluției C++ (întâlnirea post-Bellevue 2008)
  •   Doc nr. 2597: ISO/IEC DTR 19768 (29 aprilie 2008)Starea evoluției C++ (întâlnirea pre-Antipolis 2008)
  •   Doc nr. 2606: ISO/IEC DTR 19768 (19 mai 2008)Proiect de lucru, Standard pentru limbajul de programare C++
  •   Doc nr. 2697: ISO/IEC DTR 19768 (15 iunie 2008)Procesul-verbal al reuniunii WG21 8-15 iunie 2008
  •   Doc nr. 2798: ISO/IEC DTR 19768 (4 octombrie 2008)Proiect de lucru, Standard pentru limbajul de programare C++
  •   Doc nr. 2857: ISO/IEC DTR 19768 (23 martie 2009)Proiect de lucru, Standard pentru limbajul de programare C++
  •   Doc nr. 2869: ISO/IEC DTR 19768 (28 aprilie 2009)Starea evoluției C++ (întâlnirea post-San Francisco 2008)
  •   Doc nr. 3000: ISO/ISC DTR 19769 (9 noiembrie 2009)Proiect de lucru, Standard pentru limbajul de programare C++
  •   Doc nr. 3014: Stephen D. Clamage (4 noiembrie 2009)AGENDA, PL22.16 Şedinţa nr. 53, Ședința WG21 nr. 48, 8-13 martie 2010, Pittsburgh, PA
  •   Doc nr. 3082: Herb Sutter (13 martie 2010)Programul întâlnirii C++0x
  •   Doc nr. 3092: ISO/ISC DTR 19769 (26 martie 2010)Proiect de lucru, Standard pentru limbajul de programare C++
  •   Doc nr. 3126: ISO/ISC DTR 19769 (21 august 2010)Proiect de lucru, Standard pentru limbajul de programare C++
  •   Doc nr. 3225: ISO/ISC DTR 19769 (27 noiembrie 2010)Proiect de lucru, Standard pentru limbajul de programare C++
  •   Doc nr. 3242: ISO/ISC DTR 19769 (28 februarie 2011)Proiect de lucru, Standard pentru limbajul de programare C++
  •   Doc nr. 3291: ISO/ISC DTR 19769 (5 aprilie 2011)Proiect de lucru, Standard pentru limbajul de programare C++
  •   Doc nr. 3290: ISO/ISC DTR 19769 (5 aprilie 2011)FDIS, Standard pentru limbajul de programare C++
  •   Doc nr. 3337 : Data: 2012-01-16 Proiect de lucru, Standard pentru limbajul de programare C++

Link -uri

Literatură

  • Stanley B. Lippman, Josy Lajoye, Barbara E. Moo. limbaj de programare C++. Core Course Ediția a 5-a = C++ Primer (Ediția a 5-a). - M. : „Williams” , 2014. - 1120 p. - ISBN 978-5-8459-1839-0 .
  • Siddhartha Rao. Teach Yourself C++ în 21 de zile, ediția a 7-a = Sams Teach Yourself C++ într-o oră pe zi, ediția a 7-a. - M. : „Williams” , 2013. - 688 p. — ISBN 978-5-8459-1825-3 .
  • Ştefan Prata. Limbajul de programare C++ (C++11). Prelegeri și exerciții, ediția a 6-a = C++ Primer Plus, ediția a 6-a (Biblioteca dezvoltatorului). - M. : „Williams” , 2012. - 1248 p. - ISBN 978-5-8459-1778-2 .