C++11

Da Wikipedia, l'enciclopedia libera.

Il C++11, conosciuto anche come C++0x, è il nuovo standard per il linguaggio di programmazione C++ che sostituisce la revisione del 2003. Il nuovo standard comprende numerose novità per il core del linguaggio ed estenderà la libreria standard[1] incorporando molte delle librerie del cosiddetto TR1 (il “C++ Standards Committee's Library Technical Report”), già adottato da alcuni compilatori.

Il “C++ Standards Committee” ha completato il nuovo standard nel settembre 2008 e la bozza è stata presentata con il nome di N3126 il 21 agosto 2010. Il 25 marzo 2011 l'ISO vota la bozza finale (targata N3290) che viene contrassegnata come FDIS (Final Draft International Standard)[2]. Il 1º settembre 2011 viene pubblicata la versione finale del C++11 da parte di ISO e IEC (ISO/IEC 14882:2011(E) Programming Languages -- C++, Third Edition.)[3][4][5] Molte software house e progetti open source stanno sviluppando vari compilatori già funzionanti[6].

Una delle ragioni che spingono ad un processo evolutivo un linguaggio di programmazione come il C++ è la necessità di poter programmare più velocemente, elegantemente e, soprattutto, ottenendo un codice la cui manutenzione sia agevole. Questo processo porta inevitabilmente verso l'incompatibilità con il vecchio codice, per questo motivo, durante il processo di sviluppo del C++, ogni tanto si sono presentate alcune incompatibilità con le versioni precedenti. Comunque, secondo quanto annunciato da Bjarne Stroustrup (inventore del linguaggio C++, nonché membro del comitato), il nuovo standard sarà pressoché compatibile al 100% con lo standard attuale[7].

I maggiori benefici non arriveranno da soluzioni che permetteranno di scrivere meglio una linea individuale di codice, ma da quelle soluzioni che consentiranno al programmatore di risolvere un problema ed organizzare meglio il codice; così come avvenuto con l'introduzione della programmazione orientata agli oggetti ed alla programmazione generica (i template).

Indice

Le modifiche annunciate per l'imminente aggiornamento dello standard[modifica | modifica sorgente]

Come detto le modifiche al prossimo standard C++ riguarderanno sia il linguaggio di programmazione e sia la libreria standard.

Nello sviluppo di ogni utility del nuovo standard, il comitato ha applicato alcune direttive:

  • Mantenere stabilità e compatibilità con il C++98 e possibilmente anche con il C;
  • Preferire l'introduzione di nuove risorse attraverso la libreria standard, anziché estendere il core del linguaggio;
  • Prediligere le modifiche che possono evolvere il modo di programmare;
  • Rendere il C++ un linguaggio migliore per la programmazione di sistemi e librerie, piuttosto che introdurre nuove risorse dedicate soltanto ad applicazioni particolari;
  • Incrementare la sicurezza dei tipi fornendo soluzioni sicure alle correnti soluzioni insicure;
  • Incrementare le performance e le capacità di lavorare direttamente con l'hardware;
  • Fornire soluzioni adatte al mondo reale;
  • Attuare il principio “zero-overhead” (il supporto addizionale richiesto per alcune utility va impiegato solo se la utility è effettivamente utilizzata dal codice);
  • Rendere il C++ facile da insegnare e da apprendere a vantaggio dei principianti, senza rimuovere nessuna particolarità necessaria ai programmatori esperti.

L'attenzione verso i principianti è molto importante, sia perché sono e saranno sempre la maggioranza rispetto agli esperti, sia perché molti principianti non intendono approfondire la loro conoscenza del C++, limitandosi ad operare nei campi in cui sono specializzati.

Estensioni al Linguaggio di programmazione C++[modifica | modifica sorgente]

Le attenzioni migliori del comitato per il C++ sono rivolte allo sviluppo del core del linguaggio. È sull'avanzamento dei lavori di questa parte dello standard che dipende la data di presentazione del C++0x.

Spesso si critica il C++ per la gestione non sicura dei tipi. Ma il C++, anche con il prossimo standard, non potrà diventare completamente sicuro nella gestione dei tipi di dato (come il Java), perché, per fare un esempio, comporterebbe l'eliminazione dei puntatori non inizializzati e quindi snaturerebbe tutto il linguaggio. Nonostante ciò sono in molti ad insistere perché lo standard C++ preveda dei meccanismi per la gestione sicura dei puntatori. Per questo motivo il nuovo standard fornirà un supporto per gli smart pointer, ma solo attraverso la libreria standard.

Le aree dove il core del C++ sarà molto migliorato sono quelle per un miglior supporto al multithreading, alla programmazione generica, ed a meccanismi di costruzione ed inizializzazione più flessibili.

Utility per il Multitasking[modifica | modifica sorgente]

Il comitato del C++ ha intenzione di introdurre per il prossimo standard degli strumenti per la programmazione in multiprocessing e per la programmazione in multithreading.

Un supporto completo per il multiprocessing appare, per il momento, troppo dipendente dal sistema operativo utilizzato e troppo complesso, per essere risolto solo attraverso un'estensione del linguaggio. Si ritiene che il supporto alla programmazione tra processi dovrebbe essere realizzato mediante una libreria di alto livello, da preferirsi rispetto ad una libreria di basso livello (con primitive di sincronizzazione potenzialmente pericolose), poiché il comitato non intende incentivare il programmatore ad utilizzare una libreria standard potenzialmente pericolosa, invece di una libreria sicura anche se non standardizzata.

Nel prossimo standard verranno aggiunti al core del C++ alcune utilità per il multithreading, mentre lo sviluppo di una libreria per i thread resta un obiettivo meno prioritario per il comitato.

Esecuzione parallela[modifica | modifica sorgente]

Sebbene la proposta vada ancora definita, è possibile che venga introdotto nel prossimo standard (o in futuro) un meccanismo per implementare il costrutto cobegin e coend, utilizzando i thread.

Per il momento non ha molta importanza quale keyword verrà utilizzata per introdurre la nuova notazione. È importante, invece, farsi un'idea di come sarà facile, mediante poche linee di codice, implementare blocchi di istruzioni eseguibili in parallelo.

active
{
  // Primo blocco.
  {
    // ...
  }
  // Secondo blocco.
  for( int j = N ; j > 0 ; j-- )
  {
    // ...
  }
  // Terzo blocco.
  ret = funzione(parametro) ;
  // Altri blocchi.
  // ...
}

Tutti i vari blocchi vengono eseguiti in parallelo. Dopo che l'esecuzione di ognuno è terminata, il programma riprende la sua esecuzione con un singolo thread.

Chiamata di funzione asincrona[modifica | modifica sorgente]

Un'altra proposta ancora da definire, che potrebbe essere introdotta nel prossimo standard (o in futuro), è un meccanismo, mediante thread, per effettuare una chiamata asincrona ad una funzione.

La sintassi probabilmente assomiglierà a quella dell'esempio successivo:

int funzione( int parametro ) ;
// Effettua la chiamata e ritorna immediatamente.
IdThreadType<funzione> IdThread = future funzione(parametro) ;
// Attende il risultato della chiamata.
int ret = wait IdThread ;

Thread-Local Storage[modifica | modifica sorgente]

Spesso in ambiente multithread è necessario possedere delle variabili uniche per ogni thread. Questo già avviene per le variabili locali delle funzioni, mentre non avviene per le variabili globali o statiche.

È stato proposto, per il prossimo standard, un nuovo “specificatore di classe di memorizzazione”, che si aggiunge ai numerosi già esistenti (extern, static, register, auto e mutable). Il nuovo specificatore dovrebbe chiamarsi semplicemente thread (al momento nella documentazione ufficiale viene utilizzato __thread).

Gli oggetti thread sono simili agli oggetti globali. Mentre gli oggetti globali hanno un campo di esistenza che copre tutta l'esecuzione del programma, gli oggetti thread hanno un campo di esistenza limitato ad un singolo thread, al cui termine la variabile non può essere più acceduta. Un oggetto thread può essere inizializzato come una qualsiasi variabile di durata statica, eventualmente impiegando un costruttore; se possiede un distruttore, questo verrà chiamato al termine del thread.

Operazioni atomiche[modifica | modifica sorgente]

Nella multiprogrammazione non c'è solo il problema della sincronizzazione tra i vari thread, spesso un thread ha la necessità di eseguire delle operazioni senza che nessuno lo interrompa, questo può avvenire ed esempio nei sistemi Real-Time quando bisogna operare con una periferica, oppure quando il thread necessita un accesso esclusivo a tutte le variabili globali.

Per eseguire senza interruzioni una serie di operazioni (dette atomiche) è stata proposta la nuova keyword atomic, utilizzabile come nell'esempio seguente:

atomic
{
  // Operazioni atomiche.
  ...
}

Un nuovo significato per il modificatore di accesso volatile[modifica | modifica sorgente]

Attualmente[quando?] il modificatore di accesso volatile informa il compilatore che il valore di una variabile può essere modificato in qualsiasi momento, indipendentemente dalla volontà del thread; per esempio se la variabile è un registro di un dispositivo memory mapped. L'utilizzo di volatile è importante perché limita le ottimizzazioni del compilatore, altrimenti esso assumerebbe che la variabile possa essere modificata solo nel lato sinistro di un'istruzione di assegnamento.

È stato proposto di utilizzare la keyword volatile per indicare anche gli oggetti destinati alla comunicazione tra thread, o comunque condivisi tra più thread. In modo che la scrittura e la lettura di questi oggetti venga protetta automaticamente dal compilatore, mediante sincronizzazione, per evitare errori derivati da un accesso concomitante. Tuttavia questa soluzione al momento appare inappropriata se applicata ad oggetti complessi, ed è ancora in fase di discussione.

Utility per le Classi[modifica | modifica sorgente]

L'enfasi sulle caratteristiche generali delle classi del C++ è stato il motore principale per il successo di questo linguaggio di programmazione. Per questo è facile immaginare come le innovazioni più significative al core del C++ siano proprio dedicate allo sviluppo di nuove utilità rivolte ad incrementare le performance delle classi.

Operatori di conversione espliciti[modifica | modifica sorgente]

L'utilizzo implicito degli operatori di conversione di tipo rappresenta una delle insidie più frequenti per un programmatore. Per questo motivo viene consigliato di utilizzare il più possibile operatori di conversione come lo static_cast.

Una possibile soluzione a questo problema sarebbe l'eliminazione del cast implicito da parte del compilatore come suggerito da alcuni puristi (e magari anche del cast C-style, anch'esso fonte di errori). Ma questa soluzione non è praticabile, perché comporterebbe problemi di compatibilità e renderebbe il linguaggio C++ troppo verboso. Per il momento si è pensato di introdurre gli “operatori di conversione espliciti”, questi, a differenza degli operatori di conversione generici, possono essere invocati soltanto attraverso il cast C-style e lo static_cast. L'esempio successivo chiarisce la sintassi e l'utilizzo della nuova utility.

class A  {} ;
class B  {} ;
class C
{
  public:
             operator A() ;  // Utilizzabile anche implicitamente.
    explicit operator B() ;  // Utilizzabile solo esplicitamente.
} ;
 
A a ;
B b ;
C c ;
a = c ;  // OK.
b = c ;  // ERRORE: non esiste un operatore di
         // conversione utilizzabile implicitamente.
b = static_cast<B> (c) ;  // OK, conversione in stile C++.
b = (B)c ;  // OK, conversione in stile C.

Classi esplicite[modifica | modifica sorgente]

Alcune funzioni membro delle classi vengono generate automaticamente dal compilatore, se il programmatore ne omette la definizione. Tra le quali ci sono le funzioni membro speciali:

  • Costruttore di default
  • Costruttore di copie
  • L'operatore di copia mediante assegnamento (operator=)
  • Distruttore

Spesso capita che il codice automaticamente generato dal compilatore, per le funzioni membro speciali, non rispecchi la volontà del programmatore o comunque non sia desiderato. Per questo motivo è stata proposta una sintassi per disattivare la generazione implicita di funzioni membro speciali da parte del compilatore. La dichiarazione della nuova classe esplicita è semplice:

class oggetto explicit
{
  public: oggetto( int ) ;
} ;

Allo stesso modo è possibile dichiarare strutture esplicite.

Nell'esempio è importante notare come la classe oggetto possa essere dichiara solo a partire da un int, perché è assente qualsiasi altro tipo di costruttore. Altra nota importante è che la keyword explicit non entra a far parte del tipo della classe, la classe dichiarata nell'esempio resta di tipo oggetto.

Funzioni a chiamata implicita[modifica | modifica sorgente]

Per chiamare una funzione è sempre richiesto un esplicito uso delle parentesi, se la funzione non richiede parametri questa formalità può essere superata dichiarando la funzione implicit.

Una funzione chiamabile implicitamente non può, ovviamente, ricevere nessun parametro (probabilmente non sarà permesso nemmeno scrivere void all'interno della lista degli argomenti), e non deve essere confusa con un puntatore a funzione. Es:

long double pgreco() implicit
{
     return 3.14159265358979323846264338327950288L ; 
}
area = pgreco * 144 ;

Come suggerisce l'esempio, le funzioni a chiamata implicita possono essere utilizzate come interfaccia per costanti o per qualsiasi tipo di oggetto, possono inoltre essere impiegate nei template e naturalmente come funzioni membro di una classe. Anche le funzioni oggetto senza argomenti possono essere dichiarate implicite, questa è la sintassi proposta:

class fnzobj
{
  public: int operator()() implicit ;
} ;

La prima coppia di parentesi indica la funzione oggetto (ossia la “operator()”), la seconda indica l'elenco dei parametri (vuoto ovviamente).

Sequence constructors[modifica | modifica sorgente]

L'idea di base è quella di permettere l'inizializzazione dei vettori di oggetti user-defined, facile tanto quanto l'inizializzazione dei vettori di built-in-type, come nell'esempio:

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

Se invece abbiamo una classe simile alla seguente, nello standard C++98 non esiste la possibilità di inizializzare in modo così diretto e chiaro i suoi membri:

 class vettore
{
  private:
    int elementi[10] ;
 
  public:
    vettore(???)  {???}
} ;

L'idea è quella di introdurre un costruttore speciale a partire da una sequenza di oggetti:

 vettore( initializer_list<int> seq ) ;  // Costruttore da una sequenza di int.

L'inizializzatore di sequenza initializer_list è in realtà una classe template definita come segue:

 template<class E> class initializer_list
 {
   public:
     initializer_list( const E* first, const E* last   ) ; 
     initializer_list( const E* first, int      length ) ;
 
     int      size () const ;  // Numero di elementi.
     const E* begin() const ;  // L'indirizzo del primo elemento.
     const E* end  () const ;  // L'indirizzo successivo all'ultimo elemento.
 } ;

Ecco una possibile definizione del costruttore di sequenza della classe vettore:

vettore::vettore( initializer_list<int> seq )
{
  int j = 0 ;
  for( int *indice = seq.begin() ; j < 10 && indice < seq.end() ; indice++ )
  {
    elementi[j++] = *indice ;
  }
  while( j < 10 )
  {
    elementi[j++] = 0 ;
  }
}

Delegating Constructors[modifica | modifica sorgente]

Una classe può possedere molti costruttori e spesso questi debbono inizializzare allo stesso modo variabili e classi base ereditate. Capita altrettanto spesso che non sia possibile scrivere una funzione di inizializzazione, perché le classi base ed i riferimenti (&) vanno inizializzati prima di poter accedere al corpo del costruttore. Inoltre il riassegnamento è sconsigliabile per le classi ereditate e impossibile per i riferimenti. Questo porta il programmatore a dover ripetere più volte le stesse istruzioni per ogni costruttore, con risultati disastrosi sul fronte del mantenimento del codice.

È stato pertanto proposto che i costruttori possano richiamare altri costruttori, delegando a quest'ultimi il compito di inizializzare l'oggetto:

class coordinata
{
  private:
    coordinata( int ini_cx, int ini_cy, int ini_cz ):
                cx(ini_cx), cy(ini_cy), cz(ini_cz)  {}
  public:
    coordinata( const coordinata &ini ):  coordinata( ini.cx, ini.cy, ini.cz ) {}
    coordinata():  coordinata( 0, 0, 0 ) {}
 
  private:
    int cx ;
    int cy ;
    int cz ;
} ;

Nell'esempio si nota come il costruttore di copie ed il costruttore di default siano dei “Delegating Constructors”, perché affidano il compito della costruzione dell'oggetto al costruttore privato coordinata( int, int, int ).

Forwarding Constructors[modifica | modifica sorgente]

Capita spesso che derivando una classe si vogliano mantenere alcuni dei costruttori della classe base, nello standard C++98 questo è possibile soltanto ridefinendo tutti i costruttori utilizzati. La nuova proposta inserita nel core del linguaggio C++ è tanto semplice quanto utile.

class Base
{
  public:
    Base( int    ) ;
    Base( void   ) ;
    Base( double ) ;
 
    void f( int    ) ;
    void f( void   ) ;
    void f( double ) ;
} ;
 
class Derived: public Base
{
  public:
    using Base::f ;  // Eleva gli overload della funzione 'f'
                     // allo scope della classe derivata.
    void f( char ) ;  // Aggiunge un nuovo overload alla funzione 'f'.
    void f( int  ) ;  // Utilizza questo overload
                      // invece di 'Base::f( int )'.
 
    using Base::Base ;  // Sintassi proposta per elevare i costruttori
                        // della base allo scope della classe derivata.
    Derived( char ) ;  // Aggiunge un nuovo costruttore.
    Derived( int  ) ;  // Utilizza questo costruttore
                       // invece di 'Base::Base( int )'.
} ;

In pratica si tratta solo di estendere l'utilizzo di using, già utilizzabile per gli oggetti membro e per le funzioni membro, anche ai costruttori.

Operatori di assegnamento non come funzioni membro[modifica | modifica sorgente]

Per il prossimo standard C++ è stato pensato di permettere la definizione degli operatori di assegnamento anche all'esterno della dichiarazione della classe. Così il programmatore potrà creare degli operatori di assegnamento sia per i built-in-type, sia per tutti gli altri oggetti dei quali dispone soltanto di un'interfaccia.

Nell'esempio successivo l'operatore di assegnamento da double ad int viene sostituito con una funzione che approssima il risultato al valore intero più prossimo.

inline int &operator=( int &intero, long double reale )
{ 
  if( reale > 0. )
    intero = static_cast<int>(reale+0.5) ;
  else
    intero = static_cast<int>(reale-0.5) ;
 
  return intero ;
}

Un operatore di assegnamento non membro deve quindi accettare due parametri, dove il primo è necessariamente un riferimento ad un oggetto non costante.

I dettagli sull'utilizzo dell'operatore di assegnamento come funzione non membro non sono ancora stati chiariti, al momento sono al vaglio alcune soluzioni più o meno restrittive.

Diagnosi e dichiarazioni a tempo di compilazione[modifica | modifica sorgente]

Asserzioni statiche[modifica | modifica sorgente]

Le asserzioni sono espressioni booleane che esprimono una proprietà di un oggetto. Lo standard C++98 fornisce due possibilità per testare le asserzioni, la macro ASSERT e la direttiva di preprocessore #error. Però nessuna di queste è adatta per essere utilizzata nei template: la macro testa l'asserzione a tempo di esecuzione, mentre la direttiva di preprocessore testa l'asserzione a tempo di compilazione, ma prima che il template venga istanziato.

La nuova utility prevede l'introduzione di un nuovo metodo per testare le asserzioni a tempo di compilazione, mediante la nuova parola chiave static_assert. La dichiarazione assume la forma seguente:

static_assert( ''espressione-costante'', ''messaggio-di-errore'' ) ;

Ecco alcuni esempi di come la static_assert possa essere utilizzata:

static_assert( 3.14 < PGRECO && PGRECO < 3.15, "PGRECO è impreciso!" ) ;
 
template< class T >
struct Check
{
  static_assert( sizeof(int) <= sizeof(T), "T non è abbastanza grande!" ) ;
} ;

Quando l'espressione costante viene valutata false il compilatore genera un messaggio di errore. Il primo esempio rappresenta un'alternativa alla direttiva di preprocessore #error, nel secondo esempio invece l'asserzione viene valutata per ogni istanziazione della classe template Check.

Espressioni Lambda[modifica | modifica sorgente]

Le espressioni Lambda sono delle funzioni senza nome definite al momento della chiamata. Per comprendere il vantaggio di utilizzare le espressioni Lambda, immaginiamo una semplice funzione che scandisce un vettore, applicando ad ogni elemento un'operazione:

void manipola( int *inizio, int *fine )
{
  while( inizio <= fine )
  {
    *(inizio++) = 0 ;
  }
}
int vettore[10] ;
manipola( &vettore[0], &vettore[9] ) ;

Volendo utilizzare la stessa funzione con tipi diversi possiamo trasformare la funzione in una funzione template:

template< class T>
void manipola( T *inizio, T *fine )
{
  while( inizio <= fine )
  {
    *(inizio++) = 0 ;
  }
}
long double vettore[10] ;
manipola( &vettore[0], &vettore[9] ) ;

Se invece volessimo che la funzione, a seconda delle necessità, eseguisse operazioni diverse, potremmo utilizzare una enumerazione:

enum manipolazione
{
  AZZERA,
  SETTA ,
} ;
template< class T>
void manipola( T *inizio, T *fine, manipolazione mode )
{
  while( inizio <= fine )
  {
    *(inizio++) = mode == AZZERA ? 0 : 1 ;
  }
}
int vettore[10] ;
manipola( &vettore[0], &vettore[9], AZZERA ) ;

Oppure se volessimo un codice più elegante potremmo utilizzare i puntatori a funzione:

template< class T>
void manipola( T *inizio, T *fine, void (*mode)(T *) )
{
  while( inizio <= fine )
  {
    mode(inizio++) ;  // Oppure in linguaggio C: '(*mode)(inizio++) ;'.
  }
}
template< class T> void azzera( T *val )  { *val = 0 ; }
template< class T> void setta ( T *val )  { *val = 1 ; }
 
int vettore[10] ;
manipola( &vettore[0], &vettore[9], azzera<int> ) ;

Alcuni ritengono che queste soluzioni siano troppe verbose, allora si è pensato di introdurre le “espressioni Lambda”. Se vogliamo resettare tutto il vettore scriveremo semplicemente:

int vettore[10] ;
for_each( &vettore[0], &vettore[9], *_1 = 0 ) ;

for_each è una espressione Lambda unaria, perché valuta un solo elemento del vettore alla volta. _1 è il Lambda functor che assegna 0 ad ogni elemento del vettore.

È possibile eseguire anche altre operazioni per ogni elemento del vettore, ad esempio visualizzare gli elementi:

for_each( &vettore[0], &vettore[9], cout << _1 << ' ' ) ;

Se invece volessimo stampare il carattere spazio prima di ogni elemento:

for_each( &vettore[0], &vettore[9], cout << constant(' ') << _1 ) ;

constant() crea un nullary lambda functor, senza di esso il carattere spazio sarebbe stampato una sola volta.

È anche possibile utilizzare delle variabili all'interno delle espressioni Lambda. Nell'esempio successivo si inizializza l'elemento n-esimo al valore n:

int ini = 0 ;
for_each( &vettore[0], &vettore[9], *_1 = var(ini)++ ) ;

Saranno anche disponibili espressione Lambda binarie e ternarie. Ad esempio per ordinare un vettore in modo crescente potremo scrivere:

sort( &vettore[0], &vettore[9], *_1 < *_2 ) ;

L'utility delle espressioni lambda è una soluzione ancora in evoluzione che necessita di modifiche sia al core del linguaggio C++ e sia alla libreria standard. Quanto esposto copre solo in parte l'argomento delle espressioni Lambda e potrebbe discostarsi dalla versione definitiva che verrà adottata. Quella presentata, infatti, è l'attuale implementazione fornita da Boost::Lambda.

Auto e Decltype[modifica | modifica sorgente]

Spesso la dichiarazione di una variabile non è molto agevole, soprattutto quando si tratta di tipi definiti all'interno di particolari template; prendiamo ad esempio la seguente dichiarazione:

 primo< secondo<int>, terzo<float>, quarto< bool, 128 > > ret = prima.f(val) ;

Sebbene a volte sia necessario esplicitare per intero il tipo dell'oggetto che andiamo a creare, in questo caso probabilmente è superfluo, perché può essere ricavato dal tipo di ritorno della funzione.

Utilizzando la parola chiave auto informiamo il compilatore che il tipo della variabile sarà uguale a quello del secondo membro dell'espressione:

auto ret = prima.f(val) ;
auto pgreco = 3.1415926535897932384626433832795028841L ;  // long double pgreco ;

Spesso è necessario associare il tipo di una variabile a quello di un'altra, tuttavia il programmatore è obbligato a dichiarare esplicitamente il tipo di ogni variabile che dichiara. Se il problema è l'eccessiva complessità della dichiarazione, allora conviene utilizzare il typedef, ma se l'obiettivo è proprio quello di utilizzare lo stesso tipo di una variabile, qualunque esso sia, nella prossima versione del C++ si potrà utilizzare la nuova parola chiave decltype:

int indipendente ;
decltype(indipendente) dipendente ;  // int dipendente ;

Il comitato ritiene che il codice scritto utilizzando auto e decltype sia più ordinato e più mantenibile.

Utility per i Template[modifica | modifica sorgente]

Molti degli sforzi dei ricercatori sono puntati verso il miglioramento della programmazione generica, questo perché è diventata molto popolare e viene utilizzata per esprimere classi sempre più articolate, tanto da mettere in difficoltà i mezzi dell'attuale C++. È necessario quindi che il codice generico possa esprimere soluzioni di maggiore complessità, questo senza sacrificare le caratteristiche che lo hanno reso tanto diffuso: i template sono infatti facili da scrivere, leggere ed impiegare.

Alias di template attraverso la keyword using[modifica | modifica sorgente]

Attualmente[quando?] è possibile utilizzare alias di template solo se tutta la lista dei parametri è definita mediante il typedef. Non è possibile creare un alias se ci sono dei parametri indefiniti.

In alcuni casi è possibile risolvere il problema utilizzando dei parametri di default, in altri l'unica scorciatoia adottabile è creare un nuovo template che inglobi il template originario. Ma questa soluzione non risolve il problema. Dall'esempio si nota infatti che i due tipi dichiarati dalle diverse tecniche di aliasing (generica_ifc_1 e generica_ifc_2) non sono riconosciuti compatibili dal compilatore.

template< class prima, class seconda, class terza > class generica{} ;
typedef generica< int, float, char > generica_ifc_1 ;
 
template< class seconda >class generica_iXc: generica< int, seconda, char > {} ;
typedef generica_iXc<float> generica_ifc_2 ;

Il problema è importante perché l'aliasing permette un utilizzo più flessibile delle librerie template e quindi anche della STL (Standard Template Library). Per esempio:

 MyVector<int, MyAlloc<int> > vettore ;

Sarebbe più comodo avere un alias in cui basti ripetere int una sola volta. Nel prossimo standard C++, probabilmente, sarà possibile dichiarare alias di template utilizzando una nuova sintassi:

template< class T > using Vector = MyVector< T, MyAlloc<T> > ;
Vector<int> int_vettore ;

Come già permesso per i typedef, questa nuova sintassi potrà essere utilizzata anche all'interno di una classe per creare un alias membro (che quindi potrà essere public, protected o private).

Variadic Templates[modifica | modifica sorgente]

Con il nuovo standard sarà introdotta la possibilità dichiarare template con numero arbitrario di parametri. L'impatto più importante sarà senza dubbio quello di poter estendere tutti i controlli, normalmente applicati ai parametri delle normali funzioni durante la compilazione, anche a quelle funzioni con numero arbitrario di parametri.

Per indicare l'elenco dei parametri di lunghezza variabile si utilizza l'operatore “...” che può eventualmente essere seguito dall'identificatore della lista dei parametri:

template< class T, ... pack > class stock ;
typedef stock< int, float, double, string, 5, vector > type ;
 
template< ... Arg > void print_template_args( const Arg& arg... ) ;
print_template_args( int, 17, 42., "Ciao" ) ;

Sull'identificatore della lista dei parametri può essere applicata una sola operazione di disassemblaggio attraverso l'operatore “...”:

template< ... Arg > void stampa_stringa( const string& s, const Arg& arg... )
{
  printf  ( ("Debug: " + s).c_str(), arg... ) ;
  my_print( ("Debug: " + s).c_str(), arg... ) ;
}
stampa_stringa( "Eccoli: %d, %f, %c%s\n", 15, 0., 'f', "inito" ) ;
 
template< typename Primo, ... Secondi >
struct tuple
{
  Primo                   uno ;
  generica< Secondi ... > due ;
} ;
tuple< float, int, string > var ;

Nel penultimo esempio si è utilizzato la printf come esempio di variadic function, ma possono essere utilizzate anche funzioni con una normale lista di parametri, in questo caso il compilatore utilizza l'overload di my_print corrispondente all'elenco dei parametri inseriti.

Il compilatore, nell'ultimo esempio, espanderà i parametri Secondi all'interno della istanziazione della classe generica, se non esiste un overload del template che accetti due parametri, il codice è malformato e la compilazione da esito negativo. Da notare che il compilatore cercherà di istanziare generica anche se l'elenco dei parametri è vuoto, e quindi cercherà un overload del template generica senza parametri.

Concept[modifica | modifica sorgente]

Spesso i parametri che utilizziamo per un template devono possedere delle determinate caratteristiche, altrimenti rischiamo errori di compilazione, oppure (questo è grave) istanziazioni prive di logica. Nello standard attuale la definizione è l'unico controllo che assumiamo sui parametri di un template, mentre sarebbe più opportuno controllare le sue istanziazioni indipendentemente dalla definizione del template, e viceversa. 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[8].

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[modifica | modifica sorgente]

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[modifica | modifica sorgente]

typedef opaco[modifica | modifica sorgente]

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[modifica | modifica sorgente]

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++ approverà il 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[modifica | modifica sorgente]

Nello standard attuale allo “0” spetta 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. Probabilmente nel nuovo standard sarà inclusa una nuova parola chiave, la quale sarà riservata esclusivamente per indicare il puntatore nullo (per il momento è stata proposta nullptr).

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

Resta ovviamente ancora solido il ruolo della costante “0” per ragioni di compatibilità. Se la nuova sintassi avrà successo il comitato del C++ potrebbe dichiarare deprecato l'utilizzo della costante 0 come puntatore nullo, ed in un futuro remoto abolire questa doppia natura.

Sintassi esplicita per le funzioni virtuali[modifica | modifica sorgente]

La specializzazione di una funzione virtuale può portare il programmatore ad errori logici difficilmente individuabili. Oggi[quando?] per specializzare una funzione virtuale può essere omessa la parola virtual dalla dichiarazione della funzione membro della classe derivata, questa possibilità, forse inserita per non rendere troppo verboso il codice, non rappresenta affatto un vantaggio. Anzi, è fonte di ambiguità.

Leggendo solo la classe derivata possono non essere chiare le intenzioni del programmatore, e quindi portare a problemi di mantenibilità. Accidentalmente potrebbe essere omessa o inserita una specializzazione indesiderata. Per questo motivo molti programmatori inseriscono sempre la parola virtual ad ogni specializzazione.

Inoltre non è possibile, in una classe derivata, sostituire una funzione virtuale dichiarata nella base nella stessa maniera di come si sostituisce una funzione membro omonima. Utilizzando lo stesso nome della funzione virtuale, si fornisce una specializzazione della funzione. Sempre! Insomma: attualmente[quando?] non si può dichiarare una funzione membro della classe derivata, che sia omonima ad una funzione virtuale della base.

E se il programmatore non volesse che una funzione virtuale possa essere implicitamente specializzata? Lo standard attuale non permette di rilevare nessuna specializzazione indesiderata a tempo di compilazione. E se, modificata una funzione virtuale della classe base, ci dimentichiamo di aggiornare tutte le classi derivate? Oppure sbagliamo a scrivere il nome della funzione virtuale nella classe derivata? Non c'è nessun metodo di controllo da parte del compilatore!

Con il prossimo standard potrebbe essere introdotta una nuova sintassi per dichiarare funzioni virtuali e virtuali pure. La nuova sintassi permetterà di sostituire (senza specializzare) una funzione virtuale della base, e permetterà di risolvere tutte le ambiguità citate che possono essere fonte d'errore. Vediamo nel dettaglio la nuova sintassi proposta:

 struct A
 {
   virtual void f()          ;  // La classe derivata può specializzare implicitamente.
   virtual void g() = 0      ;  // La classe derivata può specializzare implicitamente.
   virtual void h(): new     ;  // La classe derivata può SOLO specializzare ESPLICITAMENTE.
   virtual void i(): new = 0 ;  // La classe derivata può SOLO specializzare ESPLICITAMENTE.
   virtual void j()          ;
   virtual void k() = 0      ;
 } ;
 struct B: A
 {
   virtual void f()      ;  // OK, sintassi corrente.
   virtual void g(): A   ;  // OK, specializzazione esplicita opzionale.
   virtual void h()      ;  // ERRORE: richiede specializzazione esplicita.
   virtual void i(): A   ;  // OK, specializzazione esplicitata.
   virtual void j(): new ;  // La classe B non specializza la funzione 'A::j'
                            // ma introduce un suo metodo virtuale.
   virtual void k(): new ;  // ATTENZIONE: la classe B resta astratta perché
                            // il metodo 'A::k' non è stato specializzato.
 } ;
 struct C: B
 {
   virtual void f(): A, B ;  // OK, specializzazione esplicita opzionale.
   virtual void g()       ;  // OK, sintassi corrente.
   virtual void i(): B    ;  // ERRORE: bisogna indicare tutte le specializzazioni(': A, B').
   virtual void j(): A    ;  // OK: viene specializzata la 'A::j', non la 'B::j'.
   virtual void k(): A    ;  // OK, così la classe C non è astratta e può essere creata.
 } ;

Per la ereditarietà multipla le cose non vanno diversamente:

 struct A1
 {
   virtual void f() const = 0 ;
   virtual void g() const = 0 ;
   virtual void h() const = 0 ;
 } ;
 struct A2
 {
   virtual void f() const          ;
   virtual void g() const: new     ;
   virtual void h() const: new = 0 ;
 } ;
 struct B: A1, A2
 {
   virtual void f() const         ;  // OK, sintassi corrente per specializzare
                                     // 'A1::f' e 'A2::f'.
 
   virtual void g() const: A1     ;  // ERRORE: bisogna indicare tutte le
                                     // specializzazioni (: A1, B1).
 
   virtual void h() const: A1, A2 ;  // OK, specializzazione esplicita di 'A1::h'
                                     // e 'A2::h' come richiesto dalla
                                     // dichiarazione della 'A2::h'.
 } ;

La “enum class”[modifica | modifica sorgente]

Nello standard C++ 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).

Per il prossimo standard non saranno 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. Due sono le nuove proposte che riguardano le enumerazioni.

  • Per gli enum attualmente[quando?] utilizzati si potrà indicare esplicitamente il tipo degli elementi della enumerazione. Il tipo dovrà essere un intero, 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.
  • Verrà introdotto un nuovo tipo di enumerazione: la enum class; che sarà fortemente tipizzata, non saranno supportati i cast impliciti e, per indicare ogni elemento, dovrà essere sempre indicato lo scope. Anche per la nuova enum potrà essere indicato esplicitamente il tipo degli elementi. Il tipo dovrà essere un intero, con o senza segno; 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.

Estensioni alla libreria standard C++[modifica | modifica sorgente]

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

Attualmente[quando?] è 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 types[modifica | modifica sorgente]

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 nuova utilità viene implementata attraverso un nuovo header 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 (ora[quando?] 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.

È 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) 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[modifica | modifica sorgente]

L'inserimento nella libreria standard del C++ delle tabelle di Hash (i contenitori associativi non ordinati) è una delle richieste più frequenti. Non furono adottate nello standard attuale (quello scritto nel 1995 e approvato nel 1998) solo per ragioni di tempo. 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 verrà amministrata soltanto mediante linear chaining. Questo perché il comitato non ritiene 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 avevano colmato la mancanza di una libreria standard per le tabelle di Hash, verrà 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 derivate dalla classe container, quindi ne ereditano tutte le funzioni, comprese quelle necessarie per accedere agli elementi come: insert, erase, begin, end.

Questa nuova utilità non richiede estensioni del linguaggio C++, solo una piccola estensione dell'header <functional> e l'introduzione degli header <unordered_set> e <unordered_map> senza modifiche a nessuna classe standard esistente; inoltre non dipende dalle altre estensioni della libreria standard.

Espressioni regolari[modifica | modifica sorgente]

Molte sono le librerie nate per gestire le espressioni regolari, più o meno standardizzate, ora, dato che l'uso di questi algoritmi si è molto diffuso tra i programmatori, è tempo che la libreria standard includa questi algoritmi utilizzando tutte le potenzialità di un linguaggio orientato agli oggetti.

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 templete
                 // '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[modifica | modifica sorgente]

La gestione dell'allocazione dinamica della memoria è sempre stata, fin dai primi computer, un punto delicato della programmazione. Molti moderni linguaggi 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.
 }

Questa utilità richiede alcuni cambiamenti all'header <memory>, ma non richiede nessuna nuova estensione del linguaggio C++.

Utilità estensibile per numeri casuali[modifica | modifica sorgente]

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 attualmente[quando?] presente è la funzione rand, ma questa non è ben definita e la sua implementazione è delegata interamente ai produttori dei compilatori. Le nuove utilità per generare numeri casuali saranno definite attraverso l'header <random>, non sono richieste modifiche di altri header né estensioni del linguaggio C++.

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 nuova 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[modifica | modifica sorgente]

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

Nome della funzione Prototipo della funzione Espressione matematica
Polinomi associati di Laguerre double assoc_laguerre( unsigned n, unsigned m, double x ) ; {L_n}^m(x) = (-1)^m \frac{d^m}{dx^m} L_{n+m}(x), \text{ per } x \ge 0
Polinomi associati di Legendre double assoc_legendre( unsigned l, unsigned m, double x ) ; {P_l}^m(x) = (1-x^2)^{m/2} \frac{d^m}{dx^m} P_l(x), \text{ per } x \ge 0
Funzione Beta di Eulero double beta( double x, double y ) ; \Beta(x,y)=\frac{\Gamma(x) \Gamma(y)}{\Gamma(x+y)}
Integrale ellittico completo di prima specie double comp_ellint_1( double k ) ; K(k) = F\left(k, \textstyle \frac{\pi}{2}\right) = \int_0^{\frac{\pi}{2}} \frac{d\theta}{\sqrt{1 - k^2 \sin^2 \theta}}
Integrale ellittico completo di seconda specie double comp_ellint_2( double k ) ; E\left(k, \textstyle \frac{\pi}{2}\right) = \int_0^{\frac{\pi}{2}} \sqrt{1 - k^2 \sin^2 \theta}\; d\theta
Integrale ellittico completo di terza specie double comp_ellint_3( double k , double nu ) ; \Pi\left(\nu, k, \textstyle \frac{\pi}{2}\right) = \int_0^{\frac{\pi}{2}} \frac{d\theta}{(1 - \nu \sin^2 \theta)\sqrt{1 - k^2 \sin^2 \theta}}
Funzione ipergeometrica confluente double conf_hyperg( double a, double c, double x ) ; F(a, c, x) = \frac{\Gamma(c)}{\Gamma(a)} \sum_{n = 0}^\infty \frac{\Gamma(a + n) x^n}{\Gamma(c + n) n!}
Funzione cilindrica di Bessel modificata regolarmente double cyl_bessel_i( double nu, double x ) ; I_\nu(x) = i^{-\nu} J_\nu(ix) = \sum_{k = 0}^\infty \frac{(x/2)^{\nu + 2k}}{k! \; \Gamma(\nu + k + 1)}, \text{ per } x \ge 0
Funzione cilindrica di Bessel di prima specie double cyl_bessel_j( double nu, double x ) ; J_\nu(x) = \sum_{k = 0}^\infty \frac{(-1)^k \; (x/2)^{\nu + 2k}}{k! \; \Gamma(\nu + k + 1)}, \text{ per } x \ge 0
Funzione cilindrica di Bessel modificata irregolarmente double cyl_bessel_k( double nu, double x ) ; \begin{align}
K_\nu(x) & = \textstyle\frac{\pi}{2} i^{\nu+1} \big(J_\nu(ix) + i N_\nu(ix)\big) \\
         & = \begin{cases}
                 \displaystyle \frac{I_{-\nu}(x) - I_\nu(x)}{\sin \nu\pi}, & \text{ per } x \ge 0 \text{ e } \nu \notin \mathbb{Z} \\[10pt]
                 \displaystyle \frac{\pi}{2} \lim_{\mu \to \nu} \frac{I_{-\mu}(x) - I_\mu(x)}{\sin \mu\pi}, & \text{ per } x < 0 \text{ e } \nu \in \mathbb{Z} \\
             \end{cases}
\end{align}
Funzione cilindrica di Neumann

Funzione cilindrica di Bessel di seconda specie

double cyl_neumann( double nu, double x ) ; 
N_\nu(x) = \begin{cases}
                 \displaystyle \frac{J_\nu(x)\cos \nu\pi - J_{-\nu}(x)}{\sin \nu\pi}, & \text{ per } x \ge 0 \text{ e } \nu \notin \mathbb{Z} \\[10pt]
                 \displaystyle \lim_{\mu \to \nu} \frac{J_\mu(x)\cos \mu\pi - J_{-\mu}(x)}{\sin \mu\pi}, & \text{ per } x < 0 \text{ e } \nu \in \mathbb{Z} \\
             \end{cases}
Integrale ellittico incompleto di prima specie double ellint_1( double k, double phi ) ; F(k,\phi)=\int_0^\phi\frac{d\theta}{\sqrt{1-k^2\sin^2\theta}}, \text{ per } \left|k\right| \le 1
Integrale ellittico incompleto di seconda specie double ellint_2( double k, double phi ) ; \displaystyle E(k,\phi)=\int_0^\phi\sqrt{1-k^2\sin^2\theta}d\theta, \text{ per } \left|k\right| \le 1
Integrale ellittico incompleto di terza specie double ellint_3( double k, double nu, double phi ) ; \Pi(k,\nu,\phi)=\int_0^\phi\frac{d\theta}{\left(1-\nu\sin^2\theta\right)\sqrt{1-k^2\sin^2\theta}}, \text{ per } \left|k\right| \le 1
Integrale esponenziale double expint( double x ) ;  \mbox{E}i(x)=-\int_{-x}^{\infty} \frac{e^{-t}}{t}\, dt
Polinomi di Hermite double hermite( unsigned n, double x ) ; H_n(x)=(-1)^n e^{x^2}\frac{d^n}{dx^n}e^{-x^2}
Serie ipergeometrica double hyperg( double a, double b, double c, double x ) ; F(a,b,c,x)=\frac{\Gamma(c)}{\Gamma(a)\Gamma(b)}\sum_{n = 0}^\infty\frac{\Gamma(a+n)\Gamma(b+n)}{\Gamma(c+n)}\frac{x^n}{n!}
Polinomi di Laguerre double laguerre( unsigned n, double x ) ; L_n(x)=\frac{e^x}{n!}\frac{d^n}{dx^n}\left(x^n e^{-x}\right), \text{ per } x \ge 0
Polinomi di Legendre double legendre( unsigned l, double x ) ; P_l(x) = {1 \over 2^l l!} {d^l \over dx^l } (x^2 -1)^l, \text{ per } \left|x\right| \le 1
Funzione zeta di Riemann double riemann_zeta( double x ) ; 
\Zeta(x) = 
          \begin{cases}
                 \displaystyle \sum_{k = 1}^\infty k^{-x}, & \text{per } x > 1 \\[10pt]
                 \displaystyle 2^x\pi^{x-1}\sin\left(\frac{x\pi}{2}\right)\Gamma(1-x)\zeta(1-x), & \text{per } x < 1 \\
             \end{cases}
Funzione sferica di Bessel di prima specie double sph_bessel( unsigned n, double x ) ; j_n(x) = \sqrt{\frac{\pi}{2x}} J_{n+1/2}(x), \text{ per } x \ge 0
Funzione sferica associata di Legendre double sph_legendre( unsigned l, unsigned m, double theta ) ;  Y_{l}^{m}(\theta, 0) \text{ where } Y_{l}^{m}(\theta, \phi) = (-1)^{m}\left[\frac{(2l+1)}{4\pi}\frac{(l-m)!}{(l+m)!}\right]^{1 \over 2} P_{l}^{m}(cos \theta)e^{im\phi}, \text{ per } |m| \leq l
Funzione sferica di Neumann

Funzione sferica di Bessel di seconda specie

double sph_neumann( unsigned n, double x ) ; n_n(x) = \left(\frac{\pi}{2x}\right)^{\frac{1}{2}}N_{n+\frac{1}{2}}(x), \text{ per } x \ge 0

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[modifica | modifica sorgente]

Il wrapper reference viene ottenuto da un'istanza della classe template reference_wrapper, 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
 }

Questa nuova utilità sarà inserita nell'header <utility> già esistente e non richiede estensioni del linguaggio C++.

Wrapper polimorfi per oggetti funzione[modifica | modifica sorgente]

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 == NULL )  // 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 sarà definita all'interno dell'header <functional>. Per questa nuova utility non sono richieste modifiche al linguaggio C++.

I type traits per la metaprogrammazione[modifica | modifica sorgente]

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[modifica | modifica sorgente]

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[modifica | modifica sorgente]

C++ Standards Committee Papers[modifica | modifica sorgente]

  • 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[modifica | modifica sorgente]

  • The C++ Source Bjarne Stroustrup (2 gennaio, 2006) A Brief Look at C++0x
  1. ^ C++11 FAQ
  2. ^ We Have FDIS! (Trip Report: March 2011 C++ Standards Meeting) « Sutter’s Mill
  3. ^ We have an international standard: C++0x is unanimously approved « Sutter’s Mill
  4. ^ ISO/IEC 14882:2011 - Information technology - Programming languages - C
  5. ^ Buy ISO/IEC 14882 ed3.0 - Information technology - Programming languages - C++ | IEC Webstore | Publication Abstract, Preview, Scope
  6. ^ When compilers
  7. ^ C/C++ Users Journal Bjarne Stroustrup (maggio, 2005) The Design of C++0x: Reinforcing C++'s proven strengths, while moving into the future
  8. ^ 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[modifica | modifica sorgente]

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