Nessun risultato. Prova con un altro termine.
Guide
Notizie
Software
Tutorial

Le novità di C++11 (C++0x)

L'ultima edizione dello standard ISO C++, aggiunge modifiche così rilevanti da farlo definire "un nuovo linguaggio" da Bjarne Stroustrup, l'autore del linguaggio di programmazione
L'ultima edizione dello standard ISO C++, aggiunge modifiche così rilevanti da farlo definire "un nuovo linguaggio" da Bjarne Stroustrup, l'autore del linguaggio di programmazione
Link copiato negli appunti

C++11 è il nuovo standard ISO C++, approvato il 12 agosto 2011. Si tratta del terzo standard ufficiale del linguaggio di programmazione C++: mentre i due precedenti standard, C++98 e C++03, non presentavano modifiche rilevanti, C++11 introduce modifiche significative, tanto che Bjarne Stroustrup, autore del linguaggio di programmazione, lo ha definito quasi un nuovo linguaggio.

Le novità introdotte riguardano sia il core sia la libreria Standard. Le elenchiamo brevemente prima di affrontarle una ad una all'interno dell'articolo:

Le novità più importanti del core

  • espressioni lambda
  • auto e decltype
  • nuova sintassi di inizializzazione degli oggetti
  • costruttori delega
  • funzioni di default e deleted
  • nullptr
  • riferimenti rvalue
  • Le novità più importanti della libreria standard

    • la presenza di nuovi algoritmi
    • nuove classi contenitore
    • operazioni atomiche
    • type traits
    • espressioni regolari
    • nuovi smart pointers
    • async()
    • una libreria multithread

    Nella prima parte dell'articolo ci occuperemo di introdurre brevemente le novità più importanti introdotte nel core mostrando un semplice esempio di utilizzo per ognuno di essi e specificando quali compilatori li supportano.

    Gli esempi riportati sono stati testati con la versione 4.6 di g++ che dispone di una implementazione per la maggior parte dei nuovi costrutti; per la corretta compilazione è necessario utilizzare il flag -std=c++0x

    Le espressioni Lambda in C++

    Una Lambda expression (lambda closure o espressione lambda) è una funzione senza nome definita al momento della chiamata. Il vantaggio principale di tale funzione consiste nell'eliminazione della chiamata a funzione quando si deve specificare una azione semplice.

    È sconsigliata nei casi in cui l'azione che si vuole specificare non è né comune né semplice: in tal caso è raccomandabile utilizzare una funzione oppure un oggetto che incapsuli una funzione. Una espressione lambda ha la forma:

    [] (parametri-di-input-della-funzione
    tipo-di-ritorno-della-funzione
    corpo-della-funzione
    

    Per definire una funzione lambda è quindi necessario specificare:

    • una capture list '[]' [&] []
    • gli argomenti (int a, int b)
    • il tipo di ritorno della funzione (opzionale) . Se non viene definito, viene utilizzato void
    • l'azione { return expression}

    Facciamo un esempio; supponiamo di voler contare le lettere maiuscole contenute in una stringa. Utilizzando for_each() per attraversare l'array di caratteri, è possibile utilizzare una lambda expression per determinare per ogni lettera se sia maiuscola o no. Nell'implementazione, ad ogni lettera maiuscola trovata viene applicata l'espressione lambda che incrementa una variabile definita all'esterno dell'espressione e passata come riferimento nella capture-list:

    È come se fosse stata definita una funzione il cui corpo è interamente inserito all'interno di un'altra funzione. La capture list [&UpperCase] UpperCase & UpperCase

    Tale costrutto è già supportato dai compilatori:

    • gcc 4.5 (1.1)
    • EDG eccp 4.1 (v0.9)
    • Intel C++ 11.0 (v1.0) e 12.0 (v1.0)
    • Micosoft Visual C 2010 (v1.0) e 2011.0 (v1.1)
    • auto e decltype

      auto è una nuova parola chiave del linguaggio che consente di dichiarare oggetti senza doverne specificare esplicitamente il tipo qualora la dichiarazione dell'oggetto includa già un inizializzatore.

      In tal caso l'utilizzo della parola chiave auto lascerà al compilatore il compito di trovare il tipo giusto dell'oggetto durante la fase di compilazione utilizzando l'inizializzatore. Di seguito sono riportati alcuni esempi:

      L'utilizzo di tale costrutto è utile quando il tipo di un oggetto ha un nome molto lungo o quando viene generato automaticamente (ad esempio nei template). Ad esempio la seguente istruzione

      void func (const std::vector& vi)
      {
        std::vector::const_iterator CI = vi.begin ();
      }
      

      potrebbe essere semplificata in questo modo:

      void func (const std::vector& vi) 
      { 
        auto CI = vi.begin (); 
      } 
      

      omettendo la dichiarazione esplicita dell'iteratore. La parola chiave auto C++11

      Per fornire una funzionalità simile a quella offerta dal vecchio auto è stato introdotto il costrutto decltype decltype

      const std::vector<int> VI;
      decltype typedef(vi.begin ()) CIT;
      Another_const_iterator CIT;
      

      Queste parole chiave sono supportate dai compilatori:

      • gcc 4.4 (1.0)
      • EDG eccp 4.1 (v1.0)
      • Intel C++ 11.0 (v1.0)
      • Microsoft Visual C 2011.0 (v1.1)
      • IBM xlc++ 11.1 (v1.0)
      • Clang (2.9)
      • Nuova sintassi di inizializzazione

        C++11 introduce una nuova sintassi di inizializzazione degli oggetti che ha lo scopo di essere più uniforme di quanto non fosse nelle precedenti versioni dello standard. C++, infatti, ha almeno quattro notazioni di inizializzazione differenti, alcune delle quali si sovrappongono:

        Inizializzazione utilizzando le parentesi tonde

        Notazione =:

        Notazione con parentesi graffe

        Per le strutture dati di tipo POD (Plain Old Data Structure), ovvero struct o classi che fungono solo da contenitori di dati:

        Notazione per inizializzare i membri di una classe all'interno del costruttore

        Differenze con C++03

        Questa moltitudine di costrutti è spesso fonte di confusione, non solo tra i novizi. In aggiunta in C++03 non è possibile inizializzare array POD con new []. C++11 cerca di fare pulizia utilizzando una notazione con parentesi graffe uniforme per tutti gli utilizzi. Supponendo di avere la classe:

        la nuova sintassi di inizializzazione prevede l'utilizzo di:

        C c {0,0} / / valido solo in C + 11

        che in C++03 equivale a:

        C c (0,0);

        Il seguente esempio mostra invece la nuova sintassi per inizializzare un array con new

        int *a = new int[3] {1, 2, 0}; // valido solo in C++11
        
        class X {
          int a [4];
          public:
            X (): a {1,2,3,4} {} // C++11, inizializzatore di un membro di tipo array
        };
        

        Per quanto riguarda i contenitori, scomparirà la lunga lista di chiamate a push_back()

        // C++11 inizializzatore contenitore
        std::vector<std::string> vs = {"prima stringa", "seconda stringa", "terza stringa"}; 
        
        std::map<std::string, std::string> persone = {{"Giuseppe Verdi", "ingegnere"}, 
                                                      {"Mario Rossi", "avvocato"}};
        

        La nuova sintassi degli inizializzatori è supportata solo da GCC a partire dalla versione 4.4.

        C++11

        class C
        {
          int a = 7; / / C + 11 solo
          public:
            C ();
        };
        

        Tale sintassi è supportata solo da:

        • GCC (dalla versione 4.7)
        • Clang (dalla versione 3.0)
        • Funzioni di default e deleted

          Una funzione di default è una funzione della quale il compilatore genera una implementazione predefinita. Un esempio è quello riportato di seguito:

          Il valore =default

          Al contrario, una funzione delete

          int func () = delete;

          Per esempio C++ dichiara automaticamente un costruttore di copia e un operatore di assegnazione per le classi. Per disabilitare la copia, dichiarare queste due funzioni membro speciali =delete

          class NOCOPY
          {
          public:
             NOCOPY & operator = (const NOCOPY &) = delete;
             NOCOPY (const NOCOPY &) = delete;
             NOCOPY() = default;
          }; 
          int main(void)
          {
            NOCOPY a;
            NOCOPY b(a); // errore di compilazione: "error: use of deleted function 'NOCOPY::NOCOPY(const NOCOPY&)'"
            return 0;
          }

          Queste tipologie di funzioni sono supportate in:

          • GCC 4.4
          • EDG ecpp 4.1
          • Intel C++ 12.0
          • Clang 3.0
          • Nella seconda parte dell'articolo continueremo ad esaminare le novità del core, come l'innovazione del nullptr (il puntatore nullo), i costruttori delega e i riferimenti rvalue.

            nullptr

            nullptr è una nuova parola chiave del linguaggio che consente di indicare un puntatore nullo. nullptr è fortemente tipizzato e sostituisce le macro NULL e 0, spesso fonti di errore. Di seguito un esempio di utilizzo che mostra come nullptr renda meno ambigue alcune chiamate di funzione:

            Se consideriamo le due funzioni:

            void f(int); // # 1
            void f(char *) ; // # 2

            e chiamando in C++03 la funzione

            f(0); // quale funzione sarà richiamata?

            è poco chiaro quale delle due funzioni verrà effettivamente chiamata in quanto entrambe potrebbero essere compatibili con l'argomento passato.

            In C++11, invece, la disponibilità di nullptr rende meno ambigua la chiamata che diventa:

            f(nullptr) // non ambigua, chiama # 2

            nullptr è applicabile a tutte le categorie di puntatori, sia puntatori a funzione che puntatori ad oggetti:

            const char *pc = str.c_str(); // Dati puntatori
            if (pc != nullptr)
              std::cout << pc << std::endl;
            
            int (A::*pmf)() = nullptr; // puntatore ad una funzione membro
            void (* PMF)() = nullptr; // puntatore a funzione

            nullptr è supportato in:

            • GCC 4.6
            • Intel C++ 12.1
            • Microsoft Visual C 2010
            • Clang 2.9
            • Costruttori Delega

              Il costruttore delega è un nuovo construtto che consente di chiamare un altro costruttore della stessa classe. Nelle precedenti versioni di C++ per avere due costruttori con lo stesso comportamento era necessario ripetere lo stesso codice codice oppure richiamare una funzione init().

              Il primo approccio è soggetto ad errori e presenta problemi di manutenibilità mentre il secondo peggiora la leggibilità del codice. Il costruttore delega dovrebbe risolvere entrambi i problemi. Di seguito è riportato un esempio di utilizzo in cui sono presenti due costruttori.

              #include <iostream> 
              class M
              {
                int x, y;
                char * p;
                public:
                  M(int v): x(v), y(0), p(new char [1]) {} // # 1 costruttore target
                  M(): M(0) {std::cout << "Costruttore delega" << std::endl;} // # 2 delegando
              }; 
              
              int main(void)
              {
                M m;
                return 0;
              }

              Il costruttore #1 richiede un parametro di tipo int

              Il costruttore delega è supportato da:

              • GCC (4.7)
              • IBM XLC++ (11.1)
              • Clang (3.0)
              • Riferimenti rvalue

                I riferimenti rvalue sono dei riferimenti a valori rvalue. Per comprendere tale costrutto è necessario tenere bene in mente la differenza tra lvalue ed rvalue: si definisce lvalue ciò che può essere utilizzato dalla parte sinistra di una operazione di un assegnamento mentre rvalue designa ciò che può essere usato nel lato destro (da cui i nomi lvalue=left-value ed rvalue=right-value). Esempi di rvalue sono costanti e letterali.

                In C++03 i riferimenti possono essere associati solo ad lvalue e non ad rvalue per prevenire la possibilità di modificare i valori di variabili temporanee che vengono distrutte prima che venga usato il loro valore. Ad esempio:

                void incr(int& a) { ++a; }
                int i = 0;
                incr(i); // i becomes 1
                incr(0); // errore: 0 non è un lvalue

                Se fosse consentito sarebbe possibile anche modificare il valore di qualche dato temporaneo o, nel peggiore dei casi, il valore di 0 potrebbe diventare 1.

                Il motivo principale per l'aggiunta di riferimenti rvalue è il costrutto move semantics. A differenza della copia tradizionale, la parola move sta a significare che un oggetto di destinazione ruba le risorse dell'oggetto di origine, lasciando la sorgente in uno stato "vuoto".

                È utile per migliorare le prestazioni dato che è in grado di rendere più performanti molte operazioni. Ad esempio la copia di un oggetto può diventare più performante nei casi in cui possa essere sostituita da un'operazione di move (spostamento). Per apprezzare i vantaggi delle prestazioni della move semantics, si consideri una operazione di swap su una stringa. Un'implementazione base potrebbe essere la seguente:

                void swap(std::string & a, std::string & b)
                {
                  std::string temp = a;
                  a = b;
                  b = temp;
                }

                Tale implementazione richiede l'esecuzione delle seguenti operazioni:

                • l'allocazione di nuova memoria
                • la copia di ogni carattere dalla stringa sorgente a quella di destinazione nelle tre operazioni di copia
                • In realtà in una operazione di swap non siamo interessati ad una vera e propria copia quanto piuttosto ad una operazione di spostamento da a a b. Utilizzando la move semantics è possibile eseguire l'operazione di copia con una sola operazione di swap tra i membri dei due dati. Un esempio di implementazione con il costrutto Move è il seguente:

                  void moveswap(std::string &a, std::string &b)
                  {
                    std::string temp=move(a); // a potrebbe essere invalidato
                    a=move(b); // b potrebbe essere invalidato
                    b=move(temp); // temp potrebbe essere invalidato
                  }

                  Per implementare una classe che supporta tale costrutto è necessario:

Ti consigliamo anche