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

C++11, la libreria standard

Le funzioni sugli insiemi e i contenitori mutuati dalle estensioni previste dal TR1
Le funzioni sugli insiemi e i contenitori mutuati dalle estensioni previste dal TR1
Link copiato negli appunti

La libreria standard C++ subì già un importante restyling nel 2003 con la Library Technical Report 1 (TR1) che introdusse nuove classi contenitore (tra cui unordered_set, unordered_map, unordered_multiset e unordered_multimap) e nuove librerie (tra cui quella dedicata alle espressioni regolari ed alle tuple). Con l'approvazione di C++11, TR1 viene incorporata ufficialmente nello standard C++, insieme a nuove librerie aggiunte dopo TR1.

Tale aggiunta sancisce l'entrata di tali funzionalità nel namespace standard (std) mentre in passato erano presenti nel namespace TR1 (std::tr1).

Le versioni iniziali delle nuove librerie standard vengono già distribuite nelle ultime implementazioni di GCC, CLang e Microsoft e sono disponibili anche dal progetto Boost. Al contrario delle funzionalità del core non è disponibile una matrice delle compatibilità in cui è riportata la compatibilità nativa dei singoli compilatori. Il codice presente in questo articolo è stato testati con g++ 4.6.

Vediamo subito quali sono le novità più importanti nella libreria standard C++11.

Nuovi algoritmi e funzioni su insiemi

La libreria Standard C++11 definisce nuovi algoritmi tra cui i più importanti sono:

  • algoritmi che simulano le operazioni sugli insiemi
  • algoritmi copy_n
  • algoritmo iota()
  • Operazioni sugli insiemi

    I nuovi algoritmi per le operazioni su insiemi sono all_of, any_of e none_of e consentono di verificare se un predicato è verificato rispettivamente da tutti gli elementi di un insieme, solo da alcuni o da nessuno.

    Tutti e tre richiedono tre argomenti:

    • il primo rappresenta un iteratore al primo elemento da considerare
    • il secondo un iteratore all'ultimo elemento da considerare
    • il terzo rappresenta il predicato, ovvero la condizione che si desidera verificare sugli elementi dell'insieme compresi tra il primo iteratore e il secondo

    La funzione all_of any_of none_of

    Ecco un semplice esempio in cui il predicato isPositive

    #include <algorithm>
    #include <vector>
    #include <iostream> 
    bool ispositive(int i) { if(i>0) return true; } 
    int main(int argc, char **argv)
    {
      std::vector <int> myVector = {-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; 
      bool allPositive = all_of(myVector.begin(), myVector.end(), ispositive); // false
      bool anyPositive = any_of(myVector.begin(), myVector.end(), ispositive); // true
      bool nonePositive = none_of(myVector.begin(), myVector.end(), ispositive); // false
      std::cout << "AllPositive: " << allPositive << std::endl;
      std::cout << "AnyPositive: " << anyPositive << std::endl;
      std::cout << "NonePositive: " << nonePositive << std::endl; 
      return 0;
    }

    Output:

    AllPositive: 0 
    AnyPositive: 1 
    NonePositive: 0
    

    copy_n

    Gli algoritmi copy_n consentono di copiare più elementi da un contenitore a un altro. La funzione copy_n richiede tre parametri:

    • il primo è un iteratore al primo elemento di un contenitore dal quale si deve
      cominciare a copiare (oppure un puntatore);
    • il secondo rappresenta il numero di elementi consecutivi che si desidera copiare;
    • il terzo un iteratore al contenitore di arrivo in cui sarà effettuata la copia.
    • Il seguente codice, ad esempio, copia un array di 5 elementi in un altro array di 5 elementi:

      #include <algorithm> 
      int main(int argc, char **argv)
      {
        int source [5] = {0,12,34,50,80};
        int target [5];
        // Copia 5 elementi dall'origine alla destinazione
        std::copy_n(source, 5, target); 
        return 0:
      }

      iota

      La funzione iota è utile ad inizializzare un array con una sequenza di valori crescenti e
      sequenziali, richiede tre parametri:

      • un iteratore al primo elemento di un contenitore;
      • un iteratore all'ultimo elemento;
      • il valore di partenza.

      iota ++

      Nell'esempio che segue, iota {10,11,12,13,14} a {'a', 'b', 'c'} c

      #include  
      int main(int argc, char **argv)
      {
        int a[5] = {0};
        char c[3] = {0};
        std::iota (a, a+5, 10); // a diventa {10,11,12,13,14}
        std::iota (c, c+3, 'a'); // {'a', 'b', 'c'}
        return 0;
      }

      Nuove classi contenitore

      Le nuove classi contenitore erano già presenti nella TR1 e adesso entrano ufficialmente nella libreria standard.

      std::array

      std::array è un nuovo contenitore standard di dimensioni prefissate. Al differenza di std::vector può ospitare solo un numero di elementi definito in fase di inizializzazione e non può crescere o decrescere.

      In altre parole non espone i metodi push_back e pop_back per aggiungere o rimuovere elementi.

      È molto simile ad un array C, ma a differenza di quest'ultimo, non è implicitamente un vero e proprio puntatore. Qualora necessario è possibile ottenere un puntatore ad un std::array ma deve essere richiesto esplicitamente con una funzione apposita.

      Nel codice che segue vediamo un esempio di uso di questo contenitore e il suo rapporto con i puntatori:

      #include <array> 
      int main(int argc, char **argv)
      {
        std::array<int,6> a = {1, 2, 3};
        a [3] = 4;
      
        int x = a [5]; // x diventa 0 perché gli elementi di default sono inizializzati a zero
        // int *p1 = a; // errore: std::array non è implicitamente un puntatore
        int *p2 = a.data (); // ok: ottenere puntatore al primo elemento 
        return 0;
      }

      Anche se può esistere uno std::array di qualunque lunghezza (anche zero) non possiamo definire la dimensione di un array implicitamente tramite un elenco di inizializzazione. Vediamo nel prossimo esempio alcune problematiche che potrebbero nascere nel caso in cui non venga specificata la lunghezza ma ci sia
      solo un elenco di inizializzatori:

      // std::array<int> a3 = {1, 2, 3}; // errore: dimensioni sconosciute/mancanti
      std::array<int,0> a0; // ok: nessun elemento
      // int * p = a0.data (); // comportamento non specificato; da evitare!

      Gli std::array sono stati pensati per essere utilizzati nello sviluppo di sistemi embedded ed altre applicazioni con vincoli di prestazioni e sicurezza. Per il resto std::array mette a disposizione le funzioni tipiche dei contenitori.

      Un'altro vantaggio di std::array rispetto agli array C è che si evitano problemi con le conversioni di base derivate.

      std :: forward_list

      std::forward_list è un nuovo contenitore standard, definito nel file di inclusione <forward_list>, e consente di rappresentare una linked-list singola.

      A differenza degli altri iteratori consente solo l'iterazione in avanti, l'inserimento di nuovi elementi dopo un elemento esistente (funzione insert_after) o la rimozione di elementi dopo un elemento
      esistente (funzione erase_after).

      L'occupazione di spazio è minima (una lista vuota dovrebbe occupare solo una word) e non fornisce una funzione size dato che non ha un membro che possa conservare la dimensione della lista. Al contrario dei contenitori usuali non dispone di una funzione back per recuperare l'ultimo elemento né di una funzione push_back per inserire un elemento alla fine.

      Ecco un semplice esempio di utilizzo che mostra il funzionamento dela funzione insert_after:

      #include <iostream>
      #include <array>
      #include <forward_list> 
      int main ()
      {
        // Inizializza una linked-list ed il suo iteratore
        std::forward_list<int> mylist;
        std::forward_list<int>::iterator it; 
      
        // Esempi di utilizzo di insert_after
        it = mylist.insert_after ( mylist.before_begin(), 1 );   // 10
        it = mylist.insert_after ( it, 3, 10 );                  // 10 20 20
        it = mylist.begin();                                     //  ^
        it = mylist.insert_after ( it, {2,3,4} );                // 10 1 2 3 20 20 
        // Print list elements
        std::cout << "La lista contiene:";
        for (int& x: mylist)
          std::cout << " " << x; 
      
        std::cout << std::endl;
        return 0;
      }

      Output:

      La lista contiene: 1 2 3 4 10 10 10

      Contenitori unordered (hash)

      Un contenitore unordered è una sorta di hash table. C++11 mette a disposizione contenitori unordered di quattro tipologie differenti:

      • unordered_map
      • unordered_set
      • unordered_multimap
      • unordered_multiset

      Il nome di questi contenitori avrebbe dovuto essere 'hash', ad esempio avremmo avuto 'hash_map' piuttosto che unordered_map unordered_map

      L'aggettivo 'unordered' si riferisce a una delle differenze principali tra una map ed una unordered_map unordered_map

      L'idea alla base di tale costrutto è fornire una versione ottimizzata di una mappa laddove possibile. Ad esempio:

      #include <map>
      #include <unordered_map>
      #include <string>
      #include <iostream> 
      int main(int argc, char **argv)
      {
        std::map<std::string,int> myMap {{"Verdi",1979}, {"Rossi",1986}};
        myMap["Argese"] = 1984; 
        for(auto x : myMap)
          std::cout << '{' << x.first << ',' << x.second << '}'; 
        std::unordered_map<std::string,int> myUnMap {{"Verdi",1979}, {"Rossi",1989}};
        myUnMap["Argese"] = 1984; 
        for(auto x : myUnMap)
          std::cout << '{' << x.first << ',' << x.second << '}'; 
        return 0;
      }

      Il primo ciclo for che scorre la map unordered_map

      La funzione di lookup è implementata in maniera molto diversa per i due contenitori: per la ricerca all'interno di una mappa sono necessarie log2 (m.size ()) ricerca all'interno di una unordered_map

      Nel caso in cui siano presenti pochi elementi (ad esempio qualche decina), è difficile dire quale delle due implementazioni risulti più veloce. Per un maggior numero di elementi (ad esempio migliaia) la ricerca in
      un unordered_map

      std::tuple

      std::tuple è una sequenza ordinata di N valori (una N-tupla) in cui N può essere una costante tra 0 e un valore molto grande definito dalla specifica implementazione nel file di inclusione <tuple>. Si può pensare ad una tupla come una struct senza nome che contiene un certo numero di membri il cui tipo è specificato nella fase di inizializzazione della tupla.

      Gli elementi di una tupla sono memorizzati in maniera compatta. I tipi di elementi di una tupla possono essere specificati in modo esplicito o possono essere dedotti (utilizzando la funzione make_tuple); è possibile accedere ai singoli elementi tramite un indice (che parte
      da 0) utilizzando la funzione std::get:

      #include <tuple>
      #include <string> 
      int main(int argc, char **argv)
      {
        // Esempi di inizializzazione di una tupla
        std::tuple<std::string,int> t2("Rossi",154);
      
        // t avrà il tipo std::tuple<string,int,double>
        auto t = make_tuple(std::string("Verdi"), 10, 1.23);    
        // Esempi di accesso ai dati di una tupla
        std::string s = std::get<0>(t);
        int x = std::get<1>(t);
        double d = std::get<2>(t); 
      
        return 0;
      }

      Le tuple vengono utilizzate tutte le volte che si desidera una lista eterogenea di elementi in fase di compilazione, ma si vuole evitare di definire una classe per contenerli. Ad esempio, std::tuple std::function std::bind

      La tupla utilizzata più di frequente è la tupla con due elementi. Tuttavia, tale tupla è supportata direttamente nella libreria standard tramite std::pair. Da C++11 in poi std::pair potrà essere considerato una specializzazione della tupla tanto è vero che può essere utilizzato per inizializzare una tupla mentre, chiaramente, non è possibile il viceversa.

Ti consigliamo anche