C ++ 11

De la Wikipedia, enciclopedia liberă.
Salt la navigare Salt la căutare

C ++ 11 , cunoscut și sub numele de C ++ 0x [1] , este un standard pentru limbajul de programare C ++ care a înlocuit revizuirea din 2003 ( C ++ 03 ). Acest standard include mai multe caracteristici noi pentru nucleul limbajului și extinde biblioteca standard [2] prin încorporarea multor biblioteci ale așa-numitului TR1 („ Raportul tehnic al bibliotecii C ++ Standards Committee's Library Report ”).

Comitetul pentru standarde C ++ ” a finalizat noul standard în septembrie 2008, iar proiectul a fost trimis sub numele N3126 pe 21 august 2010 . La 25 martie 2011 , ISO a votat proiectul final (plăcuța de înmatriculare N3290), care a fost marcat ca FDIS (Standard Draft International Standard) [3] . Versiunea finală a C ++ 11 de către ISO și IEC a fost publicată la 1 septembrie 2011 (ISO / IEC 14882: 2011 (E) Limbaje de programare - C ++, ediția a treia.) [4] [5] [6] Multe case software și proiecte open source dezvoltă diverse compilatoare care funcționează deja [7] .

Unul dintre motivele care împing un limbaj de programare precum C ++ către un proces evolutiv este necesitatea de a putea programa mai rapid, mai elegant și, mai ales, de a obține un cod ușor de întreținut. Acest proces duce inevitabil la incompatibilitate cu vechiul cod, din acest motiv, în timpul procesului de dezvoltare a C ++, ocazional au apărut unele incompatibilități cu versiunile anterioare. Conform celor anunțate de Bjarne Stroustrup (inventator al limbajului C ++ și membru al comitetului), acest standard a menținut aproape 100% compatibilitate cu standardul anterior[8] .

Cele mai mari beneficii nu provin din soluții care vă vor permite să scrieți mai bine o linie de cod individuală, ci din acele soluții care permit programatorului să rezolve o problemă și să organizeze mai bine codul; așa cum sa întâmplat cu introducerea programării orientate pe obiecte și a programării generice ( șabloane ).

Modificări anunțate pentru actualizarea standard viitoare

După cum sa menționat, modificările standardului C ++ vor afecta atât limbajul de programare, cât și biblioteca standard.

În dezvoltarea fiecărei utilități a noului standard, comitetul a aplicat câteva directive:

  • Mențineți stabilitatea și compatibilitatea cu C ++ 98 și eventual și cu C ;
  • Preferă introducerea de resurse noi prin biblioteca standard, mai degrabă decât extinderea nucleului lingvistic;
  • Preferă modificările care pot evolua modul de programare;
  • Faceți din C ++ un limbaj mai bun pentru sistemele de programare și biblioteci, mai degrabă decât introducerea de resurse noi dedicate numai aplicațiilor particulare;
  • Creșteți siguranța tipurilor oferind soluții sigure la soluțiile actuale nesigure;
  • Creșteți performanța și capacitatea de a lucra direct cu hardware;
  • Oferiți soluții potrivite pentru lumea reală;
  • Implementați principiul „zero-overhead” (suportul suplimentar necesar pentru unele utilități trebuie utilizat numai dacă utilitarul este de fapt folosit de cod);
  • Facilitarea predării și învățării C ++ în beneficiul începătorilor, fără a elimina caracteristicile speciale necesare programatorilor experimentați.

Atenția pentru începători este foarte importantă, atât pentru că aceștia sunt și vor fi întotdeauna majoritari în comparație cu experții, cât și pentru că mulți începători nu intenționează să își aprofundeze cunoștințele despre C ++, limitându-se la operarea în domeniile în care sunt specializați.

Extensii la limbajul de programare C ++

Cel mai bun accent al comitetului C ++ este pe dezvoltarea nucleului lingvistic. De progresul activității acestei părți a standardului depinde data prezentării C ++ 0x.

C ++ este adesea criticat pentru manipularea de tip nesigur. Dar C ++, chiar și cu următorul standard, nu va putea deveni complet sigur în gestionarea tipurilor de date (cum ar fi Java ), deoarece, pentru a da un exemplu, ar implica eliminarea pointerilor neinițializați și, prin urmare, ar denatura limbaj întreg. În ciuda acestui fapt, sunt mulți care insistă asupra faptului că standardul C ++ oferă mecanisme pentru manipularea în siguranță a indicatorilor. Din acest motiv, noul standard va oferi suport pentru indicatoarele inteligente , dar numai prin intermediul bibliotecii standard.

Zonele în care nucleul C ++ va fi mult îmbunătățit sunt cele pentru un suport mai bun pentru multithreading , programare generică și mecanisme mai flexibile de construcție și inițializare.

Utilitate pentru multitasking

Comitetul C ++ intenționează să introducă instrumente pentru programarea multiprocesării și programarea multithreading pentru următorul standard.

Suportul complet pentru multiprocesare pare, pentru moment, prea dependent de sistemul de operare utilizat și prea complex, pentru a fi rezolvat doar printr-o extensie a limbajului. Se crede că sprijinul pentru programarea inter- proces ar trebui realizat printr-o bibliotecă de nivel înalt, pentru a fi preferată unei biblioteci de nivel scăzut (cu primitive de sincronizare potențial periculoase), întrucât comitetul nu intenționează să stimuleze programatorul să utilizeze un standard bibliotecă potențial periculoasă, în loc de o bibliotecă sigură, chiar dacă nu standardizată.

În următorul standard, unele utilități multithreading vor fi adăugate la nucleul C ++, în timp ce dezvoltarea unei biblioteci de fire rămâne o prioritate mai mică pentru comitet.

Execuție paralelă

Deși propunerea nu a fost încă definit, este posibil ca un mecanism de implementare a cobegin și coend construct, folosind fire, vor fi introduse în standardul următor (sau în viitor).

Pentru moment nu contează cu adevărat ce cuvânt cheie va fi folosit pentru a introduce noua notație. Cu toate acestea, este important să vă faceți o idee despre cât de ușor va fi, cu câteva linii de cod, să implementați blocuri de instrucțiuni care pot fi executate în paralel.

 activ
{
  // Primul bloc.
  {
    // ...
  }
  // Al doilea bloc.
  pentru ( int j = N ; j > 0 ; j - )
  {
    // ...
  }
  // Al treilea bloc.
  ret = funcție ( parametru ) ;
  // Alte blocuri.
  // ...
}

Toate diferitele blocuri sunt executate în paralel. După ce fiecare a terminat de rulat, programul își reia executarea cu un singur fir.

Apel funcție asincronă

O altă propunere încă de definit, care poate fi introdusă în următorul standard (sau în viitor), este un mecanism filetat pentru efectuarea unui apel asincron către o funcție .

Sintaxa va semăna probabil cu cea din următorul exemplu:

 funcția int ( parametrul int ) ;
// Faceți apelul și reveniți imediat.
IdThreadType < funcție > IdThread = funcție viitoare ( parametru ) ;
// Așteptați rezultatul apelului.
int ret = wait IdThread ;

Fir de stocare locală

Adesea, într-un mediu multithreaded este necesar să existe variabile unice pentru fiecare thread. Acest lucru se întâmplă deja pentru variabilele locale ale funcțiilor, în timp ce nu se întâmplă pentru variabilele globale sau statice.

O nouă „specificator de clasa de stocare“ , a fost propus pentru următorul standard, care se adaugă la numeroasele cele deja existente ( extern , static , register , auto și mutable ). Noul specificator ar trebui pur și simplu numit thread (în prezent, __thread este utilizat în documentația oficială).

Obiectele thread sunt similare cu obiectele globale. În timp ce obiectele globale au un câmp de existență care acoperă toate execuțiile programului, obiectele thread au un câmp de existență limitat la un singur thread, la sfârșitul căruia variabila nu mai poate fi accesată. Un obiect thread poate fi inițializat ca orice variabilă cu durată statică, opțional folosind un constructor; dacă are un destructor, va fi apelat la capătul firului.

Operații atomice

În multiprogramare nu există doar problema sincronizării între diferitele fire, de multe ori un fir trebuie să efectueze operații fără ca cineva să îl întrerupă, acest lucru se poate întâmpla, de exemplu, în sistemele în timp real atunci când este necesar să funcționăm cu un periferic sau atunci când firul are nevoie de acces exclusiv la toate variabilele globale.

Pentru a efectua o serie de operații (numite atomice) fără întrerupere, a fost propus noul cuvânt cheie atomic , care poate fi folosit ca în următorul exemplu:

 atomic
{
  // Operații atomice.
  ...
}

O nouă semnificație pentru modificatorul de acces volatil

În versiunile C ++ anterioare celei de-a unsprezecea, modificatorul de acces volatile informează compilatorul că valoarea unei variabile poate fi modificată în orice moment, indiferent de voința firului; de exemplu dacă variabila este un registru al unui dispozitiv cartografiat de memorie . Utilizarea volatile este importantă deoarece limitează optimizările compilatorului, altfel s-ar presupune că variabila poate fi schimbată numai în partea stângă a unei instrucțiuni de atribuire.

S-a propus să se utilizeze cuvântul cheie volatile pentru a indica și obiecte destinate comunicării între fire sau, în orice caz, partajate între mai multe fire. Astfel, scrierea și citirea acestor obiecte sunt protejate automat de compilator, prin sincronizare, pentru a evita erorile derivate din accesul simultan. Cu toate acestea, această soluție pare în prezent inadecvată atunci când este aplicată obiectelor complexe și este încă în discuție.

Utilități pentru clase

Accentul pe caracteristicile generale ale claselor C ++ a fost principalul motor pentru succesul acestui limbaj de programare. Din acest motiv, este ușor să ne imaginăm cum cele mai importante inovații ale nucleului C ++ sunt dedicate dezvoltării de noi utilități menite să sporească performanțele claselor.

Operatori de conversie expliciți

Utilizarea implicită a operatorilor de conversie de tip este una dintre cele mai frecvente capcane pentru un programator. Din acest motiv, se recomandă să utilizați cât mai mult posibil operatori de conversie, cum ar fi static_cast .

O posibilă soluție la această problemă ar fi eliminarea exprimării implicite de către compilator așa cum a sugerat unii puristi (și poate și a distribuției în stil C, care este, de asemenea, o sursă de erori). Dar această soluție nu este viabilă, deoarece ar duce la probleme de compatibilitate și ar face ca limbajul C ++ să fie prea detaliat. Pentru moment s-a gândit introducerea „ operatorilor de conversie explicită ”, aceștia, spre deosebire de operatorii de conversie generici, pot fi invocați numai prin distribuția în stil C și static_cast . Următorul exemplu clarifică sintaxa și utilizarea noului utilitar.

 clasa A {} ;
clasa B {} ;
clasa C
{
  public :
             operatorul A () ; // Poate fi folosit și implicit.
    operator explicit B () ; // Poate fi folosit numai în mod explicit.
} ;

A a ;
B b ;
C c ;
a = c ; // BINE.
b = c ; // EROARE: Nu există niciun operator pentru
         // conversie implicit utilizabilă.
b = static_cast < B > ( c ) ; // OK, conversia în stil C ++.
b = ( B ) c ; // OK, convertiți în stil C.

Constructori de secvențe

Ideea de bază este de a permite inițializarea vectorilor de obiect definiți de utilizator, la fel de ușor ca inițializarea vectorilor de tip predefiniți, ca în exemplul:

 vector int [ 10 ] = { 0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , } ;

Dacă, pe de altă parte, avem o clasă similară cu următoarea, în standardul C ++ 98 nu există posibilitatea de a inițializa membrii atât de direct și clar:

 vectorul clasei
{
  privat :
    elemente int [ 10 ] ;

  public :
    vector ( ??? ) { ??? }
} ;

Ideea este de a introduce un constructor special pornind de la o succesiune de obiecte:

 vector ( initializer_list < int > seq ) ; // Constructor dintr-o secvență de int.

Initializatorul secvenței initializer_list este de fapt o clasă de șablon definită după cum urmează:

 template < class E > class initializer_list
 {
   public :
     initializer_list ( const E * primul , const E * ultimul ) ; 
     initializer_list ( const E * first , int length ) ;
 
     int size () const ; // Numărul de elemente.
     const E * begin () const ; // Adresa primului element.
     const E * end () const ; // Adresa care urmează ultimului element.
 } ;

Iată o posibilă definiție a constructorului de secvențe al clasei vettore :

 vector :: vector ( initializer_list < int > seq )
{
  int j = 0 ;
  for ( int * index = seq . begin () ; j < 10 && index < seq . end () ; index ++ )
  {
    elements [ j ++ ] = * index ;
  }
  while ( j < 10 )
  {
    elemente [ j ++ ] = 0 ;
  }
}

Constructori delegați

O clasă poate avea mulți constructori și deseori trebuie să inițializeze clasele de bază moștenite și variabilele în același mod. De asemenea, se întâmplă adesea că nu este posibil să scrieți o funcție de inițializare, deoarece clasele de bază și referințele (&) trebuie inițializate înainte de a putea accesa corpul constructorului. De asemenea, reatribuirea nu este recomandată pentru clasele moștenite și imposibilă pentru referințe. Acest lucru îl determină pe programator să repete aceleași instrucțiuni de mai multe ori pentru fiecare constructor, cu rezultate dezastruoase în ceea ce privește menținerea codului.

Prin urmare, s-a propus ca constructorii să poată apela alți constructori, delegându-le sarcina de inițializare a obiectului:

 clasa coordonată
{
  privat :
    coordonată ( int ini_cx , int ini_cy , int ini_cz ) :
                cx ( ini_cx ), cy ( ini_cy ), cz ( ini_cz ) {}
  public :
    coordinate ( const coordinate & ini ) : coordinate ( ini . cx , ini . cy , ini . cz ) {}
    coordonate () : coordonate ( 0 , 0 , 0 ) {}

  privat :
    int cx ;
    int cy ;
    int cz ;
} ;

În exemplu, observăm cum constructorul de copiere și constructorul implicit sunt „Delegarea constructorilor”, deoarece încredințează sarcina de a construi obiectul constructorului privat coordinata( int, int, int ) .

Constructori de expediere

De multe ori se întâmplă ca derivarea unei clase să doriți să păstrați unii dintre constructorii clasei de bază, în standardul C ++ 98 acest lucru este posibil doar prin redefinirea tuturor constructorilor utilizați. Noua propunere inserată în nucleul limbajului C ++ este la fel de simplă pe cât de utilă.

 clasa Baza
{
  public :
    Baza ( int ) ;
    Baza ( nul ) ;
    Baza ( dublă ) ;

    nul f ( int ) ;
    void f ( nul ) ;
    nul f ( dublu ) ;
} ;

clasa Derivat : baza publica
{
  public :
    folosind Base :: f ; // Creșteți supraîncărcarea funcției 'f'
                     // la sfera clasei derivate.
    nul f ( char ) ; // Adăugați o nouă suprasarcină la funcția „f”.
    nul f ( int ) ; // Folosiți această suprasarcină
                      // în loc de „Base :: f (int)”.

    folosind Base :: Base ; // Sintaxă propusă pentru ridicarea constructorilor
                        // din baza către domeniul de aplicare al clasei derivate.
    Derivat ( char ) ; // Adăugați un constructor nou.
    Derivat ( int ) ; // Folosiți acest constructor
                       // în loc de „Base :: Base (int)”.
} ;

În practică, aceasta este doar o chestiune de extindere a utilizării using , deja utilizabil pentru obiecte membre și funcții membre, constructorilor de asemenea.

Diagnostic și declarații de compilare-timp

Afirmații statice

Afirmațiile sunt expresii booleene care exprimă o proprietate a unui obiect. Standardul C ++ 98 oferă două posibilități pentru testarea afirmațiilor, macrocomanda ASSERT și directiva preprocesator #error . Cu toate acestea, niciuna dintre acestea nu este potrivită pentru utilizare în șabloane: macro testează afirmația în timp de execuție, în timp ce directiva preprocesor testează afirmația în timpul compilării, dar înainte de șablonarea este instanțiată.

Noul utilitar include introducerea unei noi metode pentru testarea afirmațiilor la compilare, utilizând noul cuvânt cheie static_assert . Declarația ia următoarea formă:

 static_assert ( '' expresie - constantă '', '' mesaj - de - eroare '');

Iată câteva exemple despre modul în care static_assert poate fi utilizat:

 static_assert ( 3.14 > PGRECO && PGRECO < 3.15 , "PGRECO este inexact!" ) ;

șablon < clasa T >
struct Verificare
{
  static_assert ( sizeof ( int ) <= sizeof ( T ), "T nu este suficient de mare!" ) ;
} ;

Când expresia constantă se evaluează la false , compilatorul generează un mesaj de eroare. Primul exemplu reprezintă o alternativă la directiva #error de #error , în al doilea exemplu afirmația este evaluată pentru fiecare instanță a clasei Check șablon.

Expresii Lambda

Expresiile Lambda sunt funcții nenumite definite în timpul apelului. Pentru a înțelege avantajul utilizării expresiilor Lambda, să ne imaginăm o funcție simplă care scanează un vector, aplicând o operație fiecărui element:

 manipulare nulă ( int * start , int * end )
{
  while ( start <= end )
  {
    * ( start ++ ) = 0 ;
  }
}
vector int [ 10 ] ;
manipulare ( & vector [ 0 ], & vector [ 9 ] ) ;

Dacă dorim să folosim aceeași funcție cu diferite tipuri, putem transforma funcția într-o funcție șablon:

 șablon < clasa T >
manipulare nulă ( T * start , T * end )
{
  while ( start <= end )
  {
    * ( start ++ ) = 0 ;
  }
}
vector dublu lung [ 10 ] ;
manipulare ( & vector [ 0 ], & vector [ 9 ] ) ;

Dacă, pe de altă parte, am dori ca funcția să efectueze operațiuni diferite după cum este necesar, am putea folosi o enumerare:

 manipulare enum
{
  CLEAR ,
  SETTA ,
} ;
șablon < clasa T >
manipulare nulă ( T * start , T * end , modul manipulare )
{
  while ( start <= end )
  {
    * ( start ++ ) = mode == RESET ? 0 : 1 ;
  }
}
vector int [ 10 ] ;
manipulare ( & vector [ 0 ], & vector [ 9 ], RESET ) ;

Sau, dacă am dori un cod mai elegant, am putea folosi pointerele funcționale:

 șablon < clasa T >
manipularea golului ( T * start , T * end , void ( * mode ) ( T * ) )
{
  while ( start <= end )
  {
    modul ( start ++ ) ; // Sau în limbajul C: „(* modul) (start ++);”.
  }
}
template < class T > void clears ( T * val ) { * val = 0 ; }
șablon < clasa T > sectă nulă ( T * val ) { * val = 1 ; }

vector int [ 10 ] ;
manipulare ( & vector [ 0 ], & vector [ 9 ], resetare < int > ) ;

Unii cred că aceste soluții sunt prea detaliate, așa că s-a gândit introducerea „ expresiilor Lambda ”. Dacă dorim să resetăm tot vectorul, vom scrie pur și simplu:

 vector int [ 10 ] ;
for_each ( & vector [ 0 ], & vector [ 9 ], [] ( int & elem ) { elem = 0 ; } ) ;

Segmentul [](int & x){ x = 0; } este expresia lambda care atribuie 0 fiecărui element al vectorului.

O expresie lambda are următoarea sintaxă:

  • Secțiunea „Captură”: între paranteze pătrate este posibilă specificarea variabilelor (sau referințelor la variabile) externe lambdei, care trebuie luate în considerare și în cadrul acesteia
  • Lista parametrilor pe care trebuie să îi primească funcția
  • Performanța funcției. Tipul valorii returnate este dedus din performanță sau setat la void dacă nu există return .

Revenind la exemplul anterior, puteți efectua și alte operații pentru fiecare element al vectorului, cum ar fi afișarea elementelor:

 for_each ( & vector [ 0 ], & vector [ 9 ], [] ( int elem ) { cout << elem << '' ; } ) ;

Variabilele „capturate” între paranteze pot fi utilizate în cadrul expresiilor Lambda. În exemplul următor captăm o referință la un și inițializăm al n-lea element la valoarea lui n:

 int n = 0 ;
for_each ( & vector [ 0 ], & vector [ 9 ], [ & n ] ( int & x ) { x = n ++ ;} ) ;

O expresie lambda poate avea orice număr de argumente. De exemplu, pentru a comanda un vector în ordine crescătoare putem scrie:

 sortare ( & vector [ 0 ], & vector [ 9 ], [] ( int a , int b ) { return a < b ; } ) ;

Versiunea prezentată nu este versiunea finală a funcțiilor lambda: cele de mai sus acoperă doar parțial subiectul expresiilor lambda, îmbunătățit în continuare în reviziile standardelor C ++ 14 și C ++ 17 .

Deducerea tipului: auto și decltype

Adesea declararea unei variabile nu este foarte ușoară, mai ales când vine vorba de tipuri definite în anumite șabloane; să luăm de exemplu următoarea declarație:

 prima <a doua <int> a treia <flotare> a patra <bool, 128 >> ret = înainte. f ( val ) ;

Deși uneori este necesar să specificăm pe deplin tipul obiectului pe care urmează să îl creăm, în acest caz este probabil superfluu, deoarece poate fi obținut din tipul returnat al funcției.

Folosind cuvântul cheie auto informăm compilatorul că tipul variabilei va fi același cu cel al celui de-al doilea membru al expresiei:

 auto ret = before . f ( val ) ;
auto pgreco = 3.1415926535897932384626433832795028841L ; // pgreco dublu lung;

Adesea este necesar să se asocieze tipul unei variabile cu cel al altei, cu toate acestea programatorul este obligat să declare în mod explicit tipul fiecărei variabile pe care o declară. Dacă problema este complexitatea excesivă a declarației, atunci este mai bine să utilizați typedef , dar dacă scopul este să utilizați același tip de variabilă, oricare ar fi aceasta, puteți utiliza noul cuvânt cheie decltype :

 int independent ;
decltype ( independent ) dependent ; // int dependent;

Comitetul consideră că codul scris folosind auto și decltype este decltype și mai decltype întreținut.

Utilitar pentru șabloane

Multe dintre eforturile cercetătorilor vizează îmbunătățirea programării generice , deoarece aceasta a devenit foarte populară și este utilizată pentru a exprima clase din ce în ce mai articulate, atât de mult încât să pună în dificultate mijloacele C ++ actuale. Prin urmare, este necesar ca codul generic să poată exprima soluții de complexitate mai mare, fără a sacrifica caracteristicile care l-au făcut atât de răspândit: șabloanele sunt de fapt ușor de scris, de citit și de utilizat.

Alias ​​șablon cu using

În versiunile C ++ anterioare celei de-a unsprezecea, este posibil să se utilizeze aliasuri șablon numai dacă întreaga listă de parametri este definită folosind typedef și nici nu este posibil să se creeze un alias dacă există parametri nedefiniți. Deși, în unele cazuri, este posibil să se rezolve problema utilizând parametrii impliciți, în altele singura comandă rapidă care poate fi adoptată, care nu rezolvă complet problema, este crearea unui nou șablon care încorporează șablonul original. În exemplul care urmează, de fapt, cele două tipuri declarate de diferitele tehnici de aliasing ( generica_ifc_1 și generica_ifc_2 ) nu sunt recunoscute ca fiind compatibile de către compilator.

 șablon <clasa întâi, clasa a doua , clasa a treia > clasă generică {};
generic typedef < int , float , char > generic_ifc_1 ;

template <class second> class generica_iXc: Generic <int, second, char> {};
generic typedef_iXc < float > generic_ifc_2 ;

Problema este importantă deoarece aliasarea permite o utilizare mai flexibilă a bibliotecilor de șabloane și, prin urmare, și a STL (Standard Template Library), de exemplu, în situații precum următoarele, ar fi mai convenabil să aveți un alias în care este suficient să repetă o dată int .

 Myvector <int, MyAlloc <int>> operator;

Pornind de la standardul C ++ 11, este posibil să declarați aliasuri șablon folosind o nouă sintaxă, care permite, de exemplu, să scrieți:

 șablon <clasa T> folosind Vector myvector = <T, MyAlloc <T>>;
Vector < int > vector_int ;

Așa cum s-a permis deja pentru typedef , această nouă sintaxă poate fi utilizată și în cadrul unei clase pentru a crea un alias membru (care poate fi, prin urmare, public , protected sau private ).

Șabloane variabile

Odată cu noul standard va fi introdusă posibilitatea de a declara șabloane cu un număr arbitrar de parametri. Cel mai important impact va fi, fără îndoială, acela de a putea extinde toate comenzile, aplicate în mod normal parametrilor funcțiilor normale în timpul compilării, chiar și acelor funcții cu un număr arbitrar de parametri.

Pentru a indica lista parametrilor cu lungime variabilă, utilizați operatorul „ ... ” care poate fi urmat de identificatorul listei de parametri:

 șablon < clasa T , clasă ... pachet > clasă stoc ;
typedef stock < int , float , double , string , 5 , vector > tip ;

șablon < typename ... Arg > 
void print_template_args ( const Arg & arg ... ) ;
print_template_args ( 'a' , 17 , 42.0 , "Bună ziua" ) ;

O singură operație de dezasamblare poate fi aplicată identificatorului listei de parametri folosind operatorul „...”:

 șablon < typename ... Arg > 
void print_string ( const string & s , const Arg & ... arg ) {
  printf ( ( "Depanare:" + s ). c_str (), arg ... ) ;
  my_print ( ( "Debug:" + s ). c_str (), arg ... ) ;
}
print_string ( "Iată-le:% d,% f,% c% s \ n " , 15 , 0. , 'f' , "start" ) ;

șablon < typename În primul rând , typename ... Secunde >
struct tuple
{
  Primul;
  generic < Secunde ... > două ;
} ;
tuplu < float , int , string > var ;

În penultimul exemplu, printf fost folosit ca exemplu de funcție variadică, dar pot fi folosite și funcții cu o listă normală de parametri, în acest caz compilatorul folosește supraîncărcarea my_print corespunzătoare listei de parametri introduși.

Compilatorul, în ultimul exemplu, va extinde parametrii Secondi în instanțierea generica clasei, dacă nu există o supraîncărcare a șablonului care acceptă doi parametri, codul este malformat și compilarea eșuează. Rețineți că compilatorul va încerca să instanțeze generica chiar dacă lista de parametri este goală, apoi va căuta o suprasolicitare de șablon generica fără parametri.

Iată un exemplu de șablon care poate fi utilizat pentru a calcula timpul de execuție al oricărei funcții care nu returnează void :

 șablon <
    typename Function , // Tipul funcției de executat
    typename ... Args // Tipul argumentelor funcției
> 
std :: pair < // Tip return: o „pereche” care conține rezultatul funcției și timpul de execuție
    typename std :: result_of < Function ( Args ...) > :: type , // Type returnat de funcție
    std :: chrono :: nanosecunde // Tipul unității de timp (nanosecunde)
> 
fun_time ( Function && f , Args && ... args ) { 
    auto t0 = std :: chrono :: high_resolution_clock :: now (); // Punct de timp înainte de executarea funcției 'f'
    auto res = f ( args ...); // Executarea funcției
    auto t1 = std :: chrono :: high_resolution_clock :: now (); // Punct de timp după executarea funcției 'f'
    auto dT = std :: chrono :: duration_cast < std :: chrono :: nanosecunde > ( t1 - t0 ); // Nanosecunde date de diferența dintre „t1” și „t0”
    returnează std :: make_pair ( res , dT ); // Returnează rezultatul „f” și timpul de execuție
}

Concept

Adesea parametrii pe care îi folosim pentru un șablon trebuie să aibă anumite caracteristici, altfel riscăm erori de compilare sau (acest lucru este grav) instanțieri fără logică. În standardul actual, definiția este singurul control pe care îl preluăm asupra parametrilor unui șablon, în timp ce ar fi mai potrivit să controlăm instanțierile acestuia indiferent de definiția șablonului și invers. Per questo fu proposta l'introduzione dei concept : un sistema per migliorare la diagnostica degli errori (quindi le prestazioni offerte dai template), senza perdere capacità espressiva. Tuttavia il gruppo ISO responsabile dello sviluppo dello standard decise di non introdurre i concept poiché ritenuti ancora immaturi rispetto alle scadenze che il gruppo stesso si era prefissato per la standardizzazione della nuova versione del linguaggio; è tuttavia probabile che tale funzionalità verrà proposta per i lavori dello standard successivo [9] .

Osserviamo la funzione template seguente che esegue un ordinamento mediante l'algoritmo di quick sort :

 template < class PUNT >
 void quick_sort ( PUNT primo , PUNT ultimo )
 {
   if ( primo < ultimo )
   {
     typename result_of < PUNT :: operator * () > pivot = * primo ;
 
     PUNT pDestro = ultimo ;
     PUNT pSinistro = primo ;
     ++ ultimo ; -- primo ;
     while ( pSinistro < pDestro )
     {
       while ( * ( -- pDestro ) > pivot ) ;
       while ( * ( ++ pSinistro ) < pivot ) ;
       if ( * pSinistro < * pDestro )
       {
         typename result_of < PUNT :: operator * () > temp ;
         temp = * pSinistro ;
         * pSinistro = * pDestro ;
         * pDestro = temp ;
       }
     }
     quick_sort ( primo , pDestro ) ;
     quick_sort ( ++ pDestro , ultimo ) ;
   }
 }

È evidente che i tipi PUNT e result_of<PUNT::operator*()> utilizzati in questa funzione devono possedere delle funzioni membro senza le quali la compilazione sarebbe impossibile.

La definizione di un Concept è composta da una lista di parametri, esplicitati come per la dichiarazione di un template, ed il corpo del Concept, una sequenza di semplici dichiarazioni che dipendono dai parametri della dichiarazione.

 concept < class PUNT > quick_restriction
 {
   PUNT a ; // Esista un costruttore di default per PUNT.
   PUNT b = a ; // Esista un costruttore di copie per PUNT.
 
   ++ a ; // PUNT possieda un operatore di incremento prefisso.
   -- a ; // PUNT possiede un operatore di decremento prefisso.
 
   bool bl = a < b ; // PUNT supporti l'operatore di confronto '<'.
 
   typename result_of < PUNT :: operator * () > e = * a ; // L'elemento del vettore abbia
                                                   // un costruttore di copie.
 
   e = * a ; // L'elemento del vettore sia fornito dell'operatore di assegnamento.
 
   bl = e < * a ; // Siano supportati gli operatori di
   bl = e > * a ; // confronto < e > per l'elemento del vettore.
 } ;

Il Concept viene introdotto nella definizione del template mediante la keyword where :

 template < class PUNT > where quick_restriction < PUNT >
 void quick_sort ( PUNT primo , PUNT ultimo ) { /*...*/ }

Con la stessa sintassi si può utilizzare la where anche all'interno della definizione di un concept per esprimere ulteriori restrizioni sulla combinazione di parametri.

Infine, all'interno della definizione della classe template, sarà possibile utilizzare i type traits (una nuova utility della libreria standard, vedi il paragrafo relativo ) per pilotare la compilazione in funzione delle caratteristiche dei parametri assegnati.

Parentesi angolari

Con l'introduzione della programmazione generica attraverso i template fu necessario introdurre un nuovo tipo di parentesi. Oltre alle parentesi tonde, quadre e graffe, sono state introdotte le parentesi angolari. Il compilatore, dal momento in cui sono state introdotte le nuove parentesi, deve discriminare quando i caratteri < e > sono utilizzati nelle espressioni logiche, quando invece sono utilizzati come operatori di inserimento ( << ) ed estrazione ( >> ), oppure, per l'appunto, quando sono utilizzati come parentesi angolari; questo ha fatto nascere ovviamente alcune ambiguità:

 typedef std :: vector < std :: vector < int > > Table ; // Ok.
 typedef std :: vector < std :: vector < bool >> Flags ; // Errore!
 
 void func ( List < B >= default_val ) ; // Errore!
 void func ( List < List < B >>= default_val ) ; // Errore!
 
 template < bool I > class X {} ;
 X < 1 > 2 > x1 ; // Errore!

L'ultimo esempio è un po' astruso ma verificabile. Sono possibili due istanziazioni della classe X : una con I==false ed una con I==true ; il codice definisce un'istanza di X in base al valore della relazione costante 1>2 (uguale a false ovviamente), ma il compilatore interpreta > come una parentesi angolare destra ed istanzia la X<true> , quindi il resto del codice diventa privo di senso e la compilazione fallisce.

La soluzione al problema è abbastanza semplice: nel prossimo standard il compilatore, dopo l'apertura di una parentesi angolare sinistra, dovrà interpretare la sequenza di caratteri >> come una doppia parentesi angolare destra, senza lasciarsi trarre in inganno dalle sequenze >= e >>= . Mentre per l'ultimo esempio il programmatore dovrà delimitare dalle parentesi tonde il contenuto della relazione costante.

 X < ( 1 > 2 ) > x1 ; // Ok.

In questo modo, dopo la parentesi tonda sinistra e fino alla parentesi tonda destra, il compilatore non riconosce più i caratteri <> come parentesi angolari.

Altre Utility

typedef opaco

Questa caratteristica è stata giudicata come non pronta per il C++09, ma da poter riproporre nel futuro dal comitato.

Capita spesso nei programmi di definire più variabili dello stesso tipo, ma che rappresentano grandezze assolutamente non compatibili tra loro. L'esempio più comune è quello di un sistema di coordinate espresso tramite valori double :

 struct punto
 {
   punto ( double x , double y , double z ) ;
   //...
 } ;

In programmi di una certa complessità si avverte la necessità di apportare una distinzione netta tra i tre tipi, soprattutto per ragioni di chiarezza e di mantenibilità del codice:

 typedef double Cx , Cy , Cz ;
 struct punto
 {
   punto ( Cx x , Cy y , Cz z ) ;
   //...
 } ;

Questa soluzione in apparenza sembra migliore della precedente ma, in pratica, il compilatore non effettua ancora nessun controllo sui parametri, i quali restano mutuamente sostituibili.

L'unica soluzione possibile è quella di definire tre nuove classi, e programmare tutti gli overload di ogni operatore ammesso. Questa soluzione è ideale, soprattutto se applicata in un programma di grande complessità, perché permette di ottenere un controllo totale sui nuovi tipi; lo sforzo per lo sviluppo dei nuovi oggetti sarebbe comunque poca cosa rispetto ai benefici ottenuti.

Tuttavia se il programmatore non necessita di un controllo così stringente, sarebbe molto più semplice introdurre un nuovo typedef che non crei un semplice alias, ma un nuovo oggetto. Per questo motivo è in fase di sviluppo il cosiddetto “typedef opaco”, mediante il quale sarà possibile creare un nuovo tipo che riceve in eredità tutte le caratteristiche del precedente, senza esserne il sostituto.

Saranno introdotti due tipi di typedef opachi: il typedef public ed il typedef private . Es:

 typedef public origine nuovo ;

Il primo permetterà ancora la conversione da origine a nuovo (ovviamente non implicita, altrimenti saremmo ancora al punto di partenza) e permetterà ancora una conversione implicita da nuovo a origine , però il nuovo tipo sarà ben distinto dal precedente.

 typedef public double Cx , Cy , Cz ;
 Cx x = 10 ;
 Cy y ;
 y = x ; // Errore!
 y = static_cast < Cy > ( x ) ; // Ok.

Il secondo è la forma più restrittiva del concetto opaco: per il nuovo tipo, se si vuole permettere delle conversioni con il tipo originario e viceversa, bisognerà definire delle funzioni ad hoc.

 typedef private double Cx , Cy , Cz ;
 Cx x = 10 ;
 Cy y ;
 y = x ; // Errore!
 y = static_cast < Cy > ( x ) ; // Errore!

La tecnica dei “typedef opachi” non è un metodo per utilizzare qualsiasi classe come se fosse una classe template. I nuovi tipi definiti in questa maniera restano degli alias degli originali seppur con qualche restrizioni.

long long int

Fin dai tempi del C c'è sempre stato un tipo integrale di troppo nel Core del linguaggio, in genere int era composto dallo stesso numero di byte della macchina sistema. Lo standard prevedeva per l' int 16 o 32 bit condannando lo short int (abbreviato short ) oppure il long int (abbreviato long ) ad un ruolo subalterno.

Nei nuovi sistemi a 64 bit i produttori di compilatori hanno messo fine a questa ridondanza di definizione assegnando definitivamente:

  • 16 bit -> short int
  • 32 bit -> int
  • 64 bit -> long int

Tuttavia, nei sistemi a 32 bit, resta radicata l'abitudine dei produttori di compilatori ad utilizzare long long int come numero a 64 bit. Il comitato del C++ si è sempre dimostrato riluttante a standardizzare nuovi tipi fondamentali che non siano anche stati adottati dal comitato del C (che gode di assoluta indipendenza da quello del C++). Ora che la dicitura è diventata uno “standard di fatto”, questo vincolo sembra possa essere finalmente superato. Il comitato C++ ha approvato long long int tra i tipi fondamentali (compreso unsigned long long int ).

D'altra parte, in futuro, questa dicitura potrebbe essere ancora utilizzata in sistemi basati su processori con registri a 16 bit per indicare, appunto, numeri a 128 bit.

Puntatore nullo

Nello standard pre-11 allo “ 0 ” spettava il doppio ruolo di costante intera e di puntatore nullo (soluzione adottata fin dagli albori del C nel 1972 ).

Per anni i programmatori hanno risposto a questa possibile ambiguità utilizzando la costante “ NULL ”, al posto dello “0”, anche per rendere il codice più comprensibile. Dal 2011 è stata inclusa una nuova parola chiave ( nullptr ) riservata esclusivamente per indicare il puntatore nullo.

Il nullptr non può essere assegnato ad un tipo intero, né confrontato con esso, mentre può essere confrontato ed assegnato a qualsiasi puntatore

Resta ovviamente possibile assegnare ad un puntatore la costante “0” per ragioni di compatibilità, tuttavia l'utilizzo di 0 e NULL è sconsigliato in ogni codice che non richieda retrocompatibilità, poiché potrebbe portare ad errori. Ad esempio, consideriamo il codice seguente:

 #include <iostream>
void foo ( const char * )
{
    std :: cout << "Puntatore" << std :: endl ;
}
void foo ( int )
{
    std :: cout << "Intero" << std :: endl ;
}

int main ()
{
    foo ( NULL ); //Probabilmente stamperà "Intero", o genererà un errore di compilazione
    foo ( nullptr ); //Questa chiamata invece senza dubbio stamperà "Puntatore"
}

La “enum class”

Nello standard C++-98 le enum non sono tipizzate come una classe, inoltre il tipo degli elementi è int e non se ne possono utilizzare altri. Possono essere utilizzati senza dichiararne lo scope e quindi non è possibile dichiarare in due distinte enumerazioni, due elementi con lo stesso nome. Inoltre gli elementi di una enumerazione possono essere convertiti implicitamente ad int e questo è da sempre causa di innumerevoli errori per il programma.

Se il programmatore vuole utilizzare dei tipi più sicuri, è obbligato a sviluppare delle classi come la seguente:

 enum costanti
 {
   //...
 } ;
 class valore
 {
   public :
     valore ( void ) {} // Costr. di default.
     valore ( const valore & ini ) : pri_val ( ini . pri_val ) {} // Costr. di copie.
     valore ( costanti ini ) : pri_val ( ini ) {}
 
     void operator = ( valore ini ) { pri_val = ini . pri_val ; }
     void operator = ( costanti ini ) { pri_val = ini ; }
 
     bool operator == ( valore val ) const { return pri_val == val . pri_val ; }
     bool operator == ( costanti val ) const { return pri_val == val ; }
     bool operator != ( valore val ) const { return pri_val != val . pri_val ; }
     bool operator != ( costanti val ) const { return pri_val != val ; }
 
   protected :
     short pri_val ;
 } ;

Le uniche azioni permesse tra oggetti di tipo valore sono l'assegnamento ed il confronto, questo dovrebbe mettere al riparo da possibili errori, a patto di convertire ogni valore della enum costanti utilizzato all'interno del codice con un oggetto valore . (Ad essere pignoli questa classe si presta facilmente ad essere trasformata in un template, per essere istanziata a partire da diverse enumerazioni).

A partire dallo standard C++11 non sono più necessarie dichiarazioni verbose come quella precedente per utilizzare una semplice enum ed avere l'appoggio del compilatore per la ricerca di possibili errori sintattici. Sono infatti state introdotte due espressioni riguardanti gli enum :

  • Per gli enum classici si potrà indicare esplicitamente il tipo degli elementi della enumerazione. Il tipo dovrà essere un "integral type" (intero o carattere), con o senza segno.
 enum E : unsigned long
 {
   E1 = 1 ,
   E2 = 2 ,
   Ebig = xFFFFFFF0ul ,
 } ;

Resta invariata la sintassi per indicare gli elementi della enum .

 E e1 = E1 ; // OK.
 E e1 = E :: E1 ; // OK.
  • Inoltre è stato introdotto un nuovo tipo di enumerazione: la enum class , fortemente tipizzata: non sono supportati i cast impliciti e, per indicare ogni elemento, è sempre necessario indicarne lo scope. Anche per la enum è possibile indicare esplicitamente il tipo degli elementi; se omesso sarà utilizzato int .
 enum class E : short
 {
   E1 ,
   E2 = 10 ,
   E3 ,
 } ;
 enum class N // Equivale ad 'enum class N: int'.
 {
   N1 ,
 } ;
 E e1 = E1 ; // Errore: è necessario specificare lo scope 'E'.
 E e2 = E :: E2 ; // OK.
 N n1 = N :: N1 ; // OK.
 bool b1 = e2 >= 100 ; // Errore: la enum class non può essere convertita
                        // ad int per il confronto, né int può essere
                        // convertito nella enum class.

Ranged-for

A partire dal C++11è stato introdotto il ranged for, un metodo più veloce per iterare attraverso gli elementi di un array o di un container. Affinché un oggetto sia iterabile mediante un range-based for è necessario che disponga di un metodo begin e un metodo end , o che esista un overload di std:begin e std::end per la classe dell'oggetto.

La sintassi di un range-based for è for ( tipo nome_elemento : nome_container) { espressioni... } ; una caratteristica molto comoda di questo ciclo è che l'elemento dichiarato è di tipo *container::iterator , pertanto è possibile lavorare direttamente sull'elemento invece che sull'iteratore.

 std :: vector < std :: string > vs { "aa" , "bb" , "cc" };
for ( auto str : vs ) //str dedotto di tipo std::string
    std :: cout << str << ' ' ;

std :: vector < int > v { 1 , 2 , 3 , 4 , 5 };
for ( auto & num : v ) //auto deduce int, pertanto num è di tipo int &
    num *= num ;

Estensioni alla libreria standard C++

Dalla Standard library del C++0x arriveranno le novità più ardite, anche se, in realtà, quasi tutte le nuove librerie non necessitano dell'aggiornamento del core e potrebbero funzionare anche sullo standard C++ corrente.

La maggior parte delle librerie che saranno introdotte sono definite nel documento “ Technical Report on C++ Library Extensions ” (chiamato anche TR1), la cui stesura definitiva risale al 2005 . Queste librerie sono già state adottate da alcuni compilatori e possono essere richiamate mediante il “ namespace std::tr1 ”.

È in preparazione un secondo technical report ( TR2 ) ma sicuramente verrà completato dopo la standardizzazione del C++0x. Per questa ragione il paragrafo corrente referenzia esclusivamente alcune delle librerie più significative introdotte attraverso il TR1.

Tuple

Le tuple sono collezioni di dimensioni prestabilite composte da oggetti di tipo eterogeneo. Gli elementi di una tupla possono essere un qualsiasi tipo di oggetto.

Questa utilità viene implementata nell'header <tuple> e beneficia di alcune estensioni del linguaggio C++, come:

  • template con lista di argomenti di lunghezza variabile,
  • riferimenti a riferimenti,
  • argomenti di default per le funzioni template (disponibili solo per le classi).

Ecco la definizione di tupla nell'header <tuple> :

 template < class T1 = unspecified ,
           class T2 = unspecified ,
           ...,
           class TM = unspecified > class tuple ;

Esempio di definizione ed uso di una tupla:

 typedef tuple < int , double , long & , const char * > tupla_di_prova ;
 long lungo = 12 ;
 tupla_di_prova prova ( 18 , 6.5 , lungo , "Ciao!" ) ;
 lungo = get < 0 > ( prova ) ; // Assegna a 'lungo' il valore 18.
 get < 3 > ( prova ) = "Bello!" ; // Modifica il quarto elemento della tupla.
 
 //È anche possibile generare una tupla sfruttando la deduzione del tipo (auto)
 // e la funzione std::make_tuple
 auto altra_tupla = std :: make_tuple ( 42 , "cavallo" , lungo , 'n' );

È possibile creare la tupla prova senza definirne il suo contenuto, ma questo solo se tutti gli oggetti della tupla possiedono il costruttore di default; inoltre si può assegnare una tupla ad un'altra: se le tuple sono dello stesso tipo, sarà necessario che tutti gli oggetti possiedano il costruttore di copie; se gli oggetti non corrispondono, sarà necessario che quelli del 2º membro siano convertibili a quelli del 1º, oppure che i tipi del 1º membro abbiano un costruttore adeguato:

 typedef tuple < int , double , string > tupla_1 t1 ;
 typedef tuple < char , short , const char * > tupla_2 t2 ( 'X' , 2 , "Hola!" ) ;
 t1 = t2 ; // Ok, i primi 2 possono essere convertiti,
            // il 3º accetta come costruttore un 'const char *'.

Sono anche disponibili gli operatori di confronto (tra tuple con uguale numero di elementi e fra tuple da due elementi e std::pair ) ed inoltre sono disponibili due espressioni per controllare le caratteristiche delle tuple (solo a tempo di compilazione):

  • tuple_size<T>::value Ritorna il valore del numero di elementi della tupla di tipo T .
  • tuple_element<I, T>::type Ritorna il tipo dell'elemento numero I della tupla di tipo T .

Tabelle di Hash

L'inserimento nella libreria standard del C++ delle tabelle di Hash (i contenitori associativi non ordinati) è stata una delle richieste più frequenti. Sebbene queste soluzioni diano rendimenti inferiori rispetto agli alberi bilanciati se utilizzate nel caso peggiore (ossia in presenza di molte collisioni), le loro performance sono migliori in molte applicazioni reali.

La gestione delle collisioni viene amministrata soltanto mediante linear chaining . Questo perché il comitato non ha ritenuto opportuno standardizzare soluzioni di open addressing che presentano parecchi problemi intrinseci (soprattutto quando è ammessa la cancellazione di elementi). A causa delle possibili omonimie con librerie non standard, che nel frattempo hanno colmato la mancanza di una libreria standard per le tabelle di Hash, è stato utilizzato il prefisso “unordered” invece di “hash”.

La nuova utility prevede 4 tipi di tabelle di Hash, differenti a seconda che accettino o no più elementi con la stessa chiave (chiavi equivalenti o chiavi uniche) ed a seconda che associno o no un valore arbitrario alla chiave.

Tipo di tabella di hash Mappatura arbitraria Chiavi equivalenti
unordered_set
unordered_multiset X
unordered_map X
unordered_multimap X X

Le nuove classi sono modellate sul concetto di container, pertanto ne implementano tutte le funzioni, comprese quelle necessarie per accedere agli elementi come: insert , erase , begin , end .

Per utilizzare le tabelle di hash è necessario includere gli headers <unordered_set> e <unordered_map> secondo necessità.

Espressioni regolari

A partire dal C++11 la libreria standard ha incluso una propria libreria per le gestione delle espressioni regolari . La nuova libreria, definita nel nuovo header <regex> , consiste in una coppia di nuove classi:

  • le espressioni regolari sono rappresentate dalle istanze della classe template basic_regex ;
  • le corrispondenze sono rappresentate dalle istanze della classe template match_results .

Per la ricerca si utilizza la funzione regex_search , mentre per la ricerca e la sostituzione si utilizza regex_replace , che fornisce una nuova stringa corretta come risultato. Gli algoritmi regex_search e regex_replace ricevono come input una espressione regolare ed una stringa di caratteri e scrivono le corrispondenze trovate nella struttura match_results .

Esempio di utilizzo di match_results :

 const char * reg_espr = "[ ,. \\ t \\ n;:]" ; // Elenco dei caratteri separatori.
 // NOTA: l'apparato delle espressioni regolari considera il [[backslash]] come il
 // compilatore C++, quindi, ad esempio, il carattere '\n' va indicato con "\\n".
 
 regex rgx ( reg_esp ) ; // 'regex' è un'istanza della classe template
                       // 'basic_regex' con argomento di tipo 'char'.
 cmatch match ; // 'cmatch' è un'istanza (predefinita) del template
                 // 'match_results' con argomento di tipo 'const char *'.
 const char * target = "Politecnico di Torino " ;
 
 // Identifica tutte le parole di 'target' delimitate dai caratteri di 'reg_espr'.
 if ( regex_search ( target , match , rgx ) == true )
 {
   // Se sono presenti delle parole separate dai caratteri specificati.
 
   for ( int a = 0 ; a < match . size () ; a ++ )
   {
     string str ( matches [ a ]. first , matches [ a ]. second ) ;
     cout << str << " \n " ;
   }
 }

La libreria “ regex ” non richiede l'alterazione di nessun header esistente e nessuna estensione del linguaggio.

Puntatori smart per usi generici

La gestione dell' allocazione dinamica della memoria è sempre stata, fin dai primi computer , un punto delicato della programmazione. Molti modernilinguaggi di programmazione (tipo il Java ) offrono strumenti per la gestione automatica della memoria .

I puntatori ordinari del C++ hanno molte interessanti proprietà:

  • è possibile copiarli,
  • assegnarli,
  • utilizzarne il loro valore,
  • utilizzare il void * come puntatore generico,
  • convertirli ad una delle classi base attraverso un cast statico ,
  • convertirli ad una delle classi derivate attraverso un cast dinamico .

Mentre i principali difetti dei puntatori ordinari del C++ sono:

  • la gestione manuale obbligata per gli oggetti allocati dinamicamente,
  • possono riferirsi ad un indirizzo non valido o non allocato della memoria .

I nuovi smart pointer mantengono le caratteristiche di forza dei puntatori ordinari eliminando le loro debolezze.

Utilizzando i puntatori shared_ptr la proprietà dell'oggetto viene ripartita egualmente a tutte le copie, all'ultima istanza rimasta viene delegata la responsabilità di distruggere l'oggetto. Per conoscere il numero di puntatori che fanno riferimento allo stesso oggetto è possibile utilizzare la use_count , una funzione membro di shared_ptr . La funzione membro reset permette di cancellare lo smart pointer. Un puntatore resettato si dice vuoto, quindi la sua funzione use_count ritorna sempre zero. Il puntatore weak_ptr non incide sul ciclo di vita dell'oggetto puntato, questo significa che in ogni momento è possibile che il weak_ptr venga invalidato. In questo modo è permesso a qualsiasi funzione o classe di mantenere un riferimento ad un oggetto senza influenzarne il ciclo di vita, a discapito però di una maggiore difficoltà di implementazione del codice.

Esempio di utilizzo dello shared_ptr :

 int main ( void )
 {
   shared_ptr < double > p_primo ( new double ) ;
 
   if ( true )
   {
     shared_ptr < double > p_copia = p_primo ;
 
     * p_copia = 21.2 ;
 
   } // Distruzione di 'p_copia' ma non del double allocato.
 
   return ; // Distruzione di 'p_primo' e di conseguenza del double allocato.
 }

Un terzo smart pointer è lo unique_ptr : un puntatore tale da esser l'unico detentore dell'area di memoria che ha allocato. Non è possibile copiare uno unique_ptr : è solo possibile trasferire la proprietà da un puntatore all'altro mediante il supporto della funzione std::move() .

È possibile utilizzare gli smart pointers includendo l'header <memory> ; è inoltre buona norma rimpiazzare l'uso di auto_ptr , deprecato in questo sin da questo standard con i nuovi smart pointers.

Utilità estensibile per numeri casuali

I computer hanno per definizione comportamento deterministico, tuttavia alcune applicazioni richiedono un comportamento non deterministico (anche se solo in apparenza) veicolato dalla generazione di numeri casuali .

La sola utilità standard presente prima del 2011 era la funzione rand , ma non è ben definita e la sua implementazione è delegata interamente ai produttori dei compilatori. Pertanto sono state introdotte nuove utilità per generare numeri casuali nell'header <random> .

I generatori di numeri casuali sono costituiti da uno stato interno, ed una funzione che elabora il risultato e porta il generatore allo stato successivo. Queste due caratteristiche costituiscono il motore del generatore. Un'altra fondamentale caratteristica è infine la distribuzione dei risultati, ossia l'intervallo e la densità della variabile aleatoria .

Attraverso il template variate_generator è possibile creare un generatore di numeri casuali selezionando il motore e la distribuzione desiderata. Si può scegliere tra i motori e le distribuzioni fornite dallo standard, oppure utilizzare mezzi propri.

  • Motori per numeri pseudocasuali

Nella nuova libreria verranno introdotti alcuni motori per la generazione di numeri pseudocasuali. Questi sono tutti dei template, in questo modo l'utente può personalizzarli come preferisce. Lo stato interno dei motori pseudocasuali è determinato attraverso un seme (generalmente un insieme di variabili). L'apparente casualità è dovuta solo dalla limitata percezione dell'utente.

classe template int/float qualità velocità dimensione stato*
linear_congruential int bassa media 1
substract_with_carry entrambi media veloce 25
mersenne_twister int buona veloce 624

* Moltiplicare il valore per la dimensione in byte del tipo utilizzato.

Le prestazioni di questi motori possono essere incrementate utilizzando la classe template discard_block , oppure possono essere combinate utilizzando la classe template xor_combine . Per comodità nell'header <random> sono definite anche alcune istanze standard di motori; un esempio è la classe mt19937 istanziata su base mersenne_twister :

 typedef mersenne_twister < '' def . dall ' implementazione '' , 32 , 624 , 397 , 31 , 0x9908b0df ,
                           11 , 7 , 0x9d2c5680 , 15 , 0xefc60000 , 18 >
         mt19937 ;
  • Motore per numeri non deterministici

Attraverso la classe random_device è possibile generare numeri non deterministici di tipo unsigned int . La sua implementazione richiederà l'utilizzo di un dispositivo il cui stato sia indipendente dal sistema che ospita l'applicazione (ad esempio da un contatore esterno non sincronizzato, oppure un trasduttore particolare) e richiederà anche l'impiego di un tradizionale motore pseudocasuale per, come si usa dire, “temprare il risultato”.

  • Distribuzioni dei numeri casuali

La libreria definisce parecchi tipi di distribuzioni, dalle distribuzioni uniformi a quelle definite dalla teoria della probabilità : uniform_int , bernoulli_distribution , geometric_distribution , poisson_distribution , binomial_distribution , uniform_real , exponential_distribution , normal_distribution e gamma_distribution . Ovviamente l'utente è libero di instanziare come preferisce le distribuzioni standard oppure di utilizzare una sua distribuzione compatibile.

Ecco un semplice esempio di implementazione:

 uniform_int < int > distribuzione ( 0 , 99 ) ;
 mt19937 motore ;
 variate_generator < mt19937 , uniform_int < int >> generatore ( motore , distribuzione );
 int casuale = generatore () ; // Assegna un valore casuale tra 0 e 99.

Funzioni matematiche speciali

L'header <math> definisce alcune funzioni matematiche abbastanza comuni:

  • trigonometriche : sin , cos , tan , asin , acos , atan e atan2 ;
  • iperboliche : sinh , cosh , tanh , asinh , acosh , atanh ;
  • esponenziali : exp , exp2 , frexp , ldexp , expm1 ;
  • logaritmiche : log10 , log2 , logb , ilogb , log1p ;
  • potenze : pow , sqrt , cbrt , hypot ;
  • speciali : erf , erfc , tgamma , lgamma .

La proposta era di aggiungere nuove funzioni alla categoria 'speciali' per colmare parecchie lacune che costringono ad utilizzare librerie non standardizzate, tuttavia tale cambiamento non è stato approvato per la versione finale del C++11. Chiaramente l'utilizzo di queste funzioni sarebbe stato limitato all'ambito ingegneristico ed alle discipline scientifiche.

La tabella seguente riporta le 23 funzioni approvate per lo standard C++11 .

Nome della funzione Prototipo della funzione Espressione matematica
Polinomi associati di Laguerre double assoc_laguerre(unsigned n, unsigned m, double x) ;
Polinomi associati di Legendre double assoc_legendre(unsigned l, unsigned m, double x) ;
Funzione beta di Eulero double beta(double x, double y) ;
Integrale ellittico completo di prima specie double comp_ellint_1(double k) ;
Integrale ellittico completo di seconda specie double comp_ellint_2(double k) ;
Integrale ellittico completo di terza specie double comp_ellint_3(double k, double nu) ;
Funzione ipergeometrica confluente double conf_hyperg(double a, double c, double x) ;
Funzione cilindrica di Bessel modificata regolarmente double cyl_bessel_i(double nu, double x) ;
Funzione cilindrica di Bessel di prima specie double cyl_bessel_j(double nu, double x) ;
Funzione cilindrica di Bessel modificata irregolarmente double cyl_bessel_k(double nu, double x) ;
Funzione cilindrica di Neumann

Funzione cilindrica di Bessel di seconda specie

double cyl_neumann(double nu, double x) ;
Integrale ellittico incompleto di prima specie double ellint_1(double k, double phi) ;
Integrale ellittico incompleto di seconda specie double ellint_2(double k, double phi) ;
Integrale ellittico incompleto di terza specie double ellint_3(double k, double nu, double phi) ;
Integrale esponenziale double expint(double x) ;
Polinomi di Hermite double hermite(unsigned n, double x) ;
Serie ipergeometrica double hyperg(double a, double b, double c, double x) ;
Polinomi di Laguerre double laguerre(unsigned n, double x) ;
Polinomi di Legendre double legendre(unsigned l, double x) ;
Funzione zeta di Riemann double riemann_zeta(double x) ;
Funzione sferica di Bessel di prima specie double sph_bessel(unsigned n, double x) ;
Funzione sferica associata di Legendre double sph_legendre(unsigned l, unsigned m, double theta) ;
Funzione sferica di Neumann

Funzione sferica di Bessel di seconda specie

double sph_neumann(unsigned n, double x) ;

Ad ogni funzione è sufficiente aggiungere il suffisso ' f' per ottenere la versione 'float' ed il suffisso ' l' per la versione ' long double '. Es:

 float sph_neumann ' 'f' ' ( unsigned n , float x ) ; long double sph_neumann ' 'l' ' ( unsigned n , long double x ) ;

Wrapper reference

Il wrapper reference viene ottenuto da un'istanza della classe template reference_wrapper nell'header <utility> , il suo utilizzo è simile al riferimento '&' previsto dal linguaggio C++. Per ottenere un wrapper reference da un oggetto qualsiasi si utilizza la funzione template ref (per un riferimento costante si usa cref ).

Il wrapper reference è utile soprattutto per le funzioni template quando vogliamo che ottengano un riferimento ai loro parametri invece di utilizzarne una copia:

 // Questa funzione ottiene il parametro 'r' per riferimento e lo incrementerà.
 void f ( int & r ) { r ++ ; }
 
 // Funzione template.
 template < class F , class P > void g ( F f , P t ) { f ( t ) ; }
 
 int main ()
 {
   int i = 0 ;
   g ( f , i ) ; // Viene istanziata 'g< void ( int &r ), int >'
                // quindi 'i' non viene modificato.
   cout << i << endl ; // Output -> 0
 
   g ( f , ref ( i ) ) ; // Viene istanziata 'g<void(int r),reference_wrapper<int>>'
                     // quindi 'i' sarà modificato.
   cout << i << endl ; // Output -> 1
 }

Wrapper polimorfi per oggetti funzione

I wrapper polimorfi per oggetti funzione ( Polymorphic Function Object Wrapper ) sono simili ai puntatori a funzione per semantica e sintassi, ma il loro utilizzo è meno vincolato e possono riferirsi indistintamente a qualsiasi funzione che possa essere chiama con argomenti compatibili con quelli del wrapper.

Attraverso l'esempio seguente è possibile comprenderne le caratteristiche:

 function < int ( int , int ) > pf ; // Creazione del wrapper tramite la classe
                                  // template 'function'.
 
 plus < int > add ; // 'plus' è dichiarato come 'template<class T> T plus( T, T ) ;'
                  // quindi 'add' è di tipo 'int add( int x, int y )'.
 
 pf = & add ; // L'assegnamento è corretto perché i
              // parametri ed il tipo di ritorno corrispondono.
 
 int a = pf ( 1 , 2 ) ; // NOTA: se il wrapper 'pf' non è riferito a nessuna
                       // funzione viene lanciata l'[[Eccezione (informatica)|eccezione]] 'bad_function_call'.
 
 function < bool ( short , short ) > pg ;
 if ( pg == nullptr ) // Sempre verificata perché 'pg' non
                      // è ancora assegnata a nessuna funzione.
 {
   bool adjacent ( long x , long y ) ;
   pg = & adjacent ; // I parametri ed il valore di ritorno sono compatibili,
                     // l'assegnamento è corretto.
   struct prova
   {
     bool operator ()( short x , short y ) ;
   } car ;
   pg = ref ( car ) ; // 'ref' è una funzione template che ritorna il wrapper
                    // della funzione membro 'operator()' di 'car'.
 }
 pf = pg ; // È corretto perché i parametri ed il valore di ritorno del
            // wrapper 'pg' sono compatibili con quelli del wrapper 'pf'.

La classe template function è definita all'interno dell'header <functional> .

I type traits per la metaprogrammazione

La metaprogrammazione consiste nel creare un programma che crei o modifichi un altro programma (o se stesso). Questo può avvenire a tempo di compilazione oppure a tempo di esecuzione. Il comitato del C++ ha deciso di introdurre una libreria che consenta la metaprogrammazione a tempo di compilazione attraverso i template.

Ecco un ottimo esempio di quello che si può già ottenere, con lo standard attuale, attraverso la metaprogrammazione: una ricorsione di istanziazioni di template per il calcolo di una potenza.

 template < int B , int N >
 struct Pow
 {
   // Chiamata ricorsiva e ricombinazione.
   enum { value = B * Pow < B , N -1 >:: value } ;
 } ;
 template < int B > struct Pow < B , 0 > // N == 0 condizione di terminazione.
 {
   enum { value = 1 } ;
 } ;
 int tre_alla_quarta = Pow < 3 , 4 >:: value ;

Molti algoritmi possono essere utilizzati indistintamente per diversi tipi di dati, per questo motivo sono stati inseriti nello standard C++ i template , in modo da supportare la programmazione generica e rendere più compatto e gestibile il codice. Tuttavia capita spesso di imbattersi in algoritmi che necessitano di informazioni sui dati utilizzati. Queste informazioni possono essere ricavate durante l'istanziazione di una qualsiasi classe template utilizzando i type traits .

I type traits sono moltissimi e possono identificare la categoria di un oggetto e tutte le caratteristiche di una classe (o di una struttura). Sono definiti nel nuovo header <type_traits> .

Nell'esempio seguente c'è la funzione template 'elabora' che a seconda del tipo di dati inseriti istanzierà uno dei due algoritmi proposti ( funzione.do_it ).

 // Primo modo di operare.
 template < bool B > struct funzione
 {
   template < class T1 , class T2 > int do_it ( T1 & , T2 & ) { /*...*/ }
 } ;
 // Secondo modo di operare.
 template <> struct funzione < true >
 {
   template < class T1 , class T2 > int do_it ()( T1 * , T2 * ) { /*...*/ }
 } ;
 
 // Istanziando 'elabora' si instanzia automaticamente il modo di operare corretto.
 template < class T1 , class T2 > int elabora ( T1 A , T2 B )
 {
   // Utilizza il secondo modo solo se 'T1' è un tipo intero e se 'T2' è
   // un tipo in virgola mobile, altrimenti utilizza il primo modo.
   return funzione < is_integral < T1 > && is_floating_point < T2 > >:: do_it ( A , B ) ;
 }

Attraverso i type traits , definiti nell'header <type_transform> , è possibile effettuare anche delle operazioni di trasformazioni sui tipi (lo static_cast ed il const_cast non sono sufficienti all'interno di un template).

Questo tipo di programmazione permette di ottenere un codice elegante e conciso; il vero punto debole di queste tecniche è il debugging: disagevole a tempo di compilazione ed molto difficile durante l'esecuzione del programma.

Metodo Uniforme per determinare i tipi di ritorno di un oggetto funzione

Determinare (a tempo di compilazione) il tipo di ritorno di una funzione oggetto template, soprattutto se dipende dai parametri della funzione stessa, non è sempre intuitivo.

Prendiamo in esempio il codice seguente:

 struct chiara
 {
   int operator ()( int ) ; // Il tipo del parametro è
   double operator ()( double ) ; // uguale al tipo ritorno.
 } ;
 
 template < class Obj > class calcolo
 {
   public :
     template < class Arg > Arg operator ()( Arg & a ) const
     {
       return membro ( a ) ;
     }
   private :
     Obj membro ;
 } ;

Istanziando la classe template calcolo , utilizzando come parametro la classe chiara (ossia istanziando calcolo<chiara> ), la funzione oggetto di calcolo avrà sempre lo stesso tipo di ritorno di quello della funzione oggetto di chiara .

Se invece istanziamo la classe calcolo utilizzando la classe confusa (ossia istanziando calcolo<confusa> ):

 struct confusa
 {
   double operator ()( int ) ; // Il tipo del parametro
   int operator ()( double ) ; // NON è uguale al tipo ritorno.
 } ;

Il tipo di ritorno della funzione oggetto di calcolo non sarà lo stesso di quello della classe confusa (potrà esserci una conversione da int a double o viceversa, a seconda dell'istanziazione di calculus<confused>.operator() ).

La nuova libreria, proposta per il prossimo standard, introduce la classe template result_of , che permette al programmatore di determinare ed utilizzare il tipo di ritorno di una funzione oggetto per qualsiasi dichiarazione. Nella versione corretta calcolo_ver2 viene impiegata la nuova utility per ricavare il tipo di ritorno della funzione oggetto:

 template < class Obj >
 class calcolo_ver2
 {
   public :
     template < class Arg >
     typename result_of < Obj ( Arg ) >:: type operator ()( Arg & a ) const
     { 
       return membro ( a ) ;
     }
   private :
     Obj membro ;
 } ;

In questo modo nelle istanziazioni della funzione oggetto di ' calcolo_ver2<confusa> ' non ci saranno più conversioni.

Il problema di determinare il tipo di ritorno di una chiamata ad un oggetto funzione è un sottoinsieme del problema più generale di determinare il tipo di risultato di un'espressione. Questo problema potrebbe essere risolto in futuro espandendo le funzionalità della typeof a tutte le casistiche possibili.

Bibliografia

C++ Standards Committee Papers

  • ISO/IEC DTR 19768 (19 ottobre, 2005 ) Doc No: N1905 Working Draft, Standard for programming Language C++
  • ISO/IEC DTR 19768 (24 giugno, 2005 ) Doc No: N1836 Draft Technical Report on C++ Library Extensions
  • Lawrence Crowl (2 maggio, 2005 ) Doc No: N1815 ISO C++ Strategic Plan for Multithreading
  • Detlef Vollmann (24 giugno, 2005 ) Doc No: N1834 A Pleading for Reasonable Parallel Processing Support in C++
  • Lawrence Crowl (25 agosto, 2005 ) Doc No: N1874 Thread-Local Storage
  • Jan Kristoffersen (21 ottobre, 2002 ) Doc No: N1401 Atomic operations with multi-threaded environments
  • Hans Boehm, Nick Maclaren (21 aprile, 2002 ) Doc No: N2016 Should volatile Acquire Atomicity and Thread Visibility Semantics?
  • Lois Goldthwaite (2 febbraio, 2004 ) Doc No: N1592 Explicit Conversion Operators
  • Francis Glassborow, Lois Goldthwaite (5 novembre, 2004 ) Doc No: N1717 explicit class and default definitions
  • Bjarne Stroustrup, Gabriel Dos Reis (11 dicembre, 2005 ) Doc No: N1919 Initializer lists
  • Herb Sutter, Francis Glassborow (6 aprile, 2006 ) Doc No: N1986 Delegating Constructors (revision 3)
  • Michel Michaud, Michael Wong (6 ottobre, 2004 ) Doc No: N1898 Forwarding and inherited constructors
  • Bronek Kozicki (9 settembre, 2004 ) Doc No: N1676 Non-member overloaded copy assignment operator
  • R. Klarer, J. Maddock, B. Dawes, H. Hinnant (20 ottobre, 2004 ) Doc No: N1720 Proposal to Add Static Assertions to the Core Language (Revision 3)
  • V Samko; J Willcock, J Järvi, D Gregor, A Lumsdaine (26 febbraio, 2006 ) Doc No: N1968 Lambda expressions and closures for C++
  • J. Järvi, B. Stroustrup, D. Gregor, J. Siek, G. Dos Reis (12 settembre, 2004 ) Doc No: N1705 Decltype (and auto)
  • B. Stroustrup, G. Dos Reis, Mat Marcus, Walter E. Brown, Herb Sutter (7 aprile, 2003 ) Doc No: N1449 Proposal to add template aliases to C++
  • Douglas Gregor, Jaakko Järvi, Gary Powell (10 settembre, 2004 ) Doc No: N1704 Variadic Templates: Exploring the Design Space
  • Gabriel Dos Reis, Bjarne Stroustrup (20 ottobre, 2005 ) Doc No: N1886 Specifying C++ concepts
  • Daveed Vandevoorde (14 gennaio, 2005 ) Doc No: N1757 Right Angle Brackets (Revision 2)
  • Walter E. Brown (18 ottobre, 2005 ) Doc No: N1891 Progress toward Opaque Typedefs for C++0X
  • J. Stephen Adamczyk (29 aprile, 2005 ) Doc No: N1811 Adding the long long type to C++ (Revision 3)
  • Chris Uzdavinis, Alisdair Meredith (29 agosto, 2005 ) Doc No: N1827 An Explicit Override Syntax for C++
  • Herb Sutter, David E. Miller (21 ottobre, 2004 ) Doc No: N1719 Strongly Typed Enums (revision 1)
  • Matthew Austern (9 aprile, 2003 ) Doc No: N1456 A Proposal to Add Hash Tables to the Standard Library (revision 4)
  • Doug Gregor (8 novembre, 2002 ) Doc No: N1403 Proposal for adding tuple types into the standard library
  • John Maddock (3 marzo, 2003 ) Doc No: N1429 A Proposal to add Regular Expression to the Standard Library
  • P. Dimov, B. Dawes, G. Colvin (27 marzo, 2003 ) Doc No: N1450 A Proposal to Add General Purpose Smart Pointers to the Library Technical Report (Revision 1)
  • Doug Gregor (22 ottobre, 2002 ) Doc No: N1402 A Proposal to add a Polymorphic Function Object Wrapper to the Standard Library
  • D. Gregor, P. Dimov (9 aprile, 2003 ) Doc No: N1453 A proposal to add a reference wrapper to the standard library (revision 1)
  • John Maddock (3 marzo, 2003 ) Doc No: N1424 A Proposal to add Type Traits to the Standard Library
  • Daveed Vandevoorde (18 aprile, 2003 ) Doc No: N1471 Reflective Metaprogramming in C++
  • Jens Maurer (10 aprile, 2003 ) Doc No: N1452 A Proposal to Add an Extensible Random Number Facility to the Standard Library (Revision 2)
  • Walter E. Brown (28 ottobre, 2003 ) Doc No: N1542 A Proposal to Add Mathematical Special Functions to the C++ Standard Library (version 3)
  • Douglas Gregor, P. Dimov (9 aprile, 2003 ) Doc No: N1454 A uniform method for computing function object return types (revision 1)

Articoli

  • The C++ Source Bjarne Stroustrup (2 gennaio, 2006 ) A Brief Look at C++0x
  1. ^ C++11 - cppreference.com , su en.cppreference.com . URL consultato il 13 marzo 2020 .
  2. ^ C++11 FAQ , su www2.research.att.com . URL consultato il 2 novembre 2010 (archiviato dall' url originale l'11 maggio 2011) .
  3. ^ We Have FDIS! (Trip Report: March 2011 C++ Standards Meeting) « Sutter's Mill
  4. ^ We have an international standard: C++0x is unanimously approved « Sutter's Mill
  5. ^ ISO/IEC 14882:2011 - Information technology - Programming languages - C
  6. ^ Buy ISO/IEC 14882 ed3.0 - Information technology - Programming languages - C++ | IEC Webstore | Publication Abstract, Preview, Scope
  7. ^ When compilers , su www2.research.att.com . URL consultato il 2 novembre 2010 (archiviato dall' url originale l'11 maggio 2011) .
  8. ^ C/C++ Users Journal Bjarne Stroustrup (maggio, 2005 ) The Design of C++0x: Reinforcing C++'s proven strengths, while moving into the future
  9. ^ Bjarne Stroustrup Expounds on Concepts and the Future of C
  • Web Log di Raffaele Rialdi (16 settembre, 2005 ) Il futuro di C++ raccontato da Herb Sutter
  • Informit.com (5 agosto, 2006 ) The Explicit Conversion Operators Proposal
  • Informit.com (25 luglio, 2006 ) Introducing the Lambda Library
  • Dr. Dobb's Portal Pete Becker (11 aprile, 2006 ) Regular Expressions TR1's regex implementation
  • Informit.com (25 luglio, 2006 ) The Type Traits Library
  • Dr. Dobb's Portal Pete Becker (11 maggio, 2005 ) C++ Function Objects in TR1

Collegamenti esterni

Informatica Portale Informatica : accedi alle voci di Wikipedia che trattano di informatica