C++11Il C++11, conosciuto precedentemente come C++0x[1], è uno standard per il linguaggio di programmazione C++ che ha sostituito la revisione del 2003 (C++03). Questo standard comprende numerose novità per il core del linguaggio ed estende la libreria standard[2] incorporando molte delle librerie del cosiddetto TR1 (il “C++ Standards Committee's Library Technical Report”). 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 ha votato la bozza finale (targata N3290) che è stata contrassegnata come FDIS (Final Draft International Standard)[3]. Il 1º settembre 2011 è stata pubblicata la versione finale del C++11 da parte di ISO e IEC (ISO/IEC 14882:2011(E) Programming Languages—C++, Third Edition.)[4][5][6] Molte software house e progetti open source stanno sviluppando vari compilatori già funzionanti[7]. 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. Secondo quanto annunciato da Bjarne Stroustrup (inventore del linguaggio C++, nonché membro del comitato), questo standard ha mantenuto pressoché al 100% la compatibilità con lo standard precedente[8]. I maggiori benefici non arrivano da soluzioni che permetteranno di scrivere meglio una linea individuale di codice, ma da quelle soluzioni che consentono 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). Le modifiche annunciate per l'imminente aggiornamento dello standardCome detto le modifiche allo 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:
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++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. Utilità per il MultitaskingIl 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 parallelaSebbene 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 asincronaUn'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 StorageSpesso 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 ( Gli oggetti Operazioni atomicheNella 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
{
// Operazioni atomiche.
...
}
Un nuovo significato per il modificatore di accesso volatileNelle versioni di C++ precedenti all'undicesima, il modificatore di accesso È stato proposto di utilizzare la keyword Utilità per le ClassiL'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 esplicitiL'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 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 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.
Sequence constructorsL'idea di base è quella di permettere l'inizializzazione dei vettori di oggetti definiti dall'utente, facile tanto quanto l'inizializzazione dei vettori di tipi predefiniti, 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 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( 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 ;
}
}
Costruttori delegatiUna 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 questi 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 Forwarding ConstructorsCapita 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 Diagnosi e dichiarazioni a tempo di compilazioneAsserzioni staticheLe asserzioni sono espressioni booleane che esprimono una proprietà di un oggetto.
Lo standard C++98 fornisce due possibilità per testare le asserzioni, la macro 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( ''espressione-costante'', ''messaggio-di-errore'' ) ;
Ecco alcuni esempi di come la 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 Espressioni LambdaLe 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], [](int & elem){ elem = 0; } ) ;
Il segmento Un'espressione lambda presenta la seguente sintassi:
Tornando all'esempio precedente, è possibile eseguire anche altre operazioni per ogni elemento del vettore, ad esempio visualizzare gli elementi: for_each( &vettore[0], &vettore[9], [](int elem){ cout << elem << ' '; } ) ;
Le variabili 'catturate' fra le quadre possono essere utilizzate all'interno delle espressioni Lambda. Nell'esempio successivo si cattura un riferimento a n e si inizializza l'elemento n-esimo al valore di n: int n = 0 ;
for_each( &vettore[0], &vettore[9], [&n](int & x) { x = n++;} ) ;
Un'espressione lambda può avere un qualsiasi numero di argomenti. Ad esempio per ordinare un vettore in modo crescente potremo scrivere: sort( &vettore[0], &vettore[9], [](int a, int b){ return a < b; } ) ;
La versione presentata non è la versione definitiva delle funzioni lambda: quanto esposto copre solo in parte l'argomento delle espressioni lambda, ulteriormente migliorate nelle revisioni dello standard C++14 e C++17. Deduzione del tipo: auto e decltypeSpesso 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 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 int indipendente ;
decltype(indipendente) dipendente ; // int dipendente ;
Il comitato ritiene che il codice scritto utilizzando Utilità per i TemplateMolti 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 con |
Tipo di tabella di hash | Mappatura arbitraria | Chiavi equivalenti |
---|---|---|
unordered_set | ||
unordered_multiset | X | |
unordered_map | X | |
unordered_multimap | X | X |
Le nuove classi sono modellate sul concetto di container, pertanto ne implementano tutte le funzioni, comprese quelle necessarie per accedere agli elementi come: insert
, erase
, begin
, end
.
Per utilizzare le tabelle di hash è necessario includere gli headers <unordered_set>
e <unordered_map>
secondo necessità.
Espressioni regolari
A partire dal C++11 la libreria standard ha incluso una propria libreria per le gestione delle espressioni regolari. La nuova libreria, definita nel nuovo header <regex>
, consiste in una coppia di nuove classi:
- le espressioni regolari sono rappresentate dalle istanze della classe template
basic_regex
; - le corrispondenze sono rappresentate dalle istanze della classe template
match_results
.
Per la ricerca si utilizza la funzione regex_search
, mentre per la ricerca e la sostituzione si utilizza regex_replace
, che fornisce una nuova stringa corretta come risultato.
Gli algoritmi regex_search
e regex_replace
ricevono come input una espressione regolare ed una stringa di caratteri e scrivono le corrispondenze trovate nella struttura match_results
.
Esempio di utilizzo di match_results
:
const char *reg_espr = "[ ,.\\t\\n;:]" ; // Elenco dei caratteri separatori.
// NOTA: l'apparato delle espressioni regolari considera il [[backslash]] come il
// compilatore C++, quindi, ad esempio, il carattere '\n' va indicato con "\\n".
regex rgx(reg_esp) ; // 'regex' è un'istanza della classe template
// 'basic_regex' con argomento di tipo 'char'.
cmatch match ; // 'cmatch' è un'istanza (predefinita) del template
// 'match_results' con argomento di tipo 'const char *'.
const char *target = "Politecnico di Torino " ;
// Identifica tutte le parole di 'target' delimitate dai caratteri di 'reg_espr'.
if( regex_search( target, match, rgx ) == true )
{
// Se sono presenti delle parole separate dai caratteri specificati.
for( int a = 0 ; a < match.size() ; a++ )
{
string str( matches[a].first, matches[a].second ) ;
cout << str << "\n" ;
}
}
La libreria “regex
” non richiede l'alterazione di nessun header esistente e nessuna estensione del linguaggio.
Puntatori smart per usi generici
La gestione dell'allocazione dinamica della memoria è sempre stata, fin dai primi computer, un punto delicato della programmazione. Molti 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.
}
Un terzo smart pointer è lo unique_ptr
: un puntatore tale da esser l'unico detentore dell'area di memoria che ha allocato. Non è possibile copiare uno unique_ptr
: è solo possibile trasferire la proprietà da un puntatore all'altro mediante il supporto della funzione std::move()
.
È possibile utilizzare gli smart pointers includendo l'header <memory>
; è inoltre buona norma rimpiazzare l'uso di auto_ptr
, deprecato in questo sin da questo standard con i nuovi smart pointers.
Utilità estensibile per numeri casuali
I computer hanno per definizione comportamento deterministico, tuttavia alcune applicazioni richiedono un comportamento non deterministico (anche se solo in apparenza) veicolato dalla generazione di numeri casuali.
La sola utilità standard presente prima del 2011 era la funzione rand
, ma non è ben definita e la sua implementazione è delegata interamente ai produttori dei compilatori. Pertanto sono state introdotte nuove utilità per generare numeri casuali nell'header <random>
.
I generatori di numeri casuali sono costituiti da uno stato interno, ed una funzione che elabora il risultato e porta il generatore allo stato successivo. Queste due caratteristiche costituiscono il motore del generatore. Un'altra fondamentale caratteristica è infine la distribuzione dei risultati, ossia l'intervallo e la densità della variabile aleatoria.
Attraverso il template variate_generator
è possibile creare un generatore di numeri casuali selezionando il motore e la distribuzione desiderata. Si può scegliere tra i motori e le distribuzioni fornite dallo standard, oppure utilizzare mezzi propri.
- Motori per numeri pseudocasuali
Nella nuova libreria verranno introdotti alcuni motori per la generazione di numeri pseudocasuali. Questi sono tutti dei template, in questo modo l'utente può personalizzarli come preferisce. Lo stato interno dei motori pseudocasuali è determinato attraverso un seme (generalmente un insieme di variabili). L'apparente casualità è dovuta solo dalla limitata percezione dell'utente.
classe template | int/float | qualità | velocità | dimensione stato* |
---|---|---|---|---|
linear_congruential | int | bassa | media | 1 |
substract_with_carry | entrambi | media | veloce | 25 |
mersenne_twister | int | buona | veloce | 624 |
* Moltiplicare il valore per la dimensione in byte del tipo utilizzato.
Le prestazioni di questi motori possono essere incrementate utilizzando la classe template discard_block
, oppure possono essere combinate utilizzando la classe template xor_combine
.
Per comodità nell'header <random>
sono definite anche alcune istanze standard di motori; un esempio è la classe mt19937
istanziata su base mersenne_twister
:
typedef mersenne_twister< ''def.dall'implementazione'', 32, 624, 397, 31, 0x9908b0df,
11, 7, 0x9d2c5680, 15, 0xefc60000, 18 >
mt19937 ;
- Motore per numeri non deterministici
Attraverso la classe random_device
è possibile generare numeri non deterministici di tipo unsigned int
. La sua implementazione richiederà l'utilizzo di un dispositivo il cui stato sia indipendente dal sistema che ospita l'applicazione (ad esempio da un contatore esterno non sincronizzato, oppure un trasduttore particolare) e richiederà anche l'impiego di un tradizionale motore pseudocasuale per, come si usa dire, “temprare il risultato”.
- Distribuzioni dei numeri casuali
La libreria definisce parecchi tipi di distribuzioni, dalle distribuzioni uniformi a quelle definite dalla teoria della probabilità: uniform_int
, bernoulli_distribution
, geometric_distribution
, poisson_distribution
, binomial_distribution
, uniform_real
, exponential_distribution
, normal_distribution
e gamma_distribution
. Ovviamente l'utente è libero di instanziare come preferisce le distribuzioni standard oppure di utilizzare una sua distribuzione compatibile.
Ecco un semplice esempio di implementazione:
uniform_int<int> distribuzione( 0, 99 ) ;
mt19937 motore ;
variate_generator<mt19937, uniform_int<int>> generatore( motore, distribuzione );
int casuale = generatore() ; // Assegna un valore casuale tra 0 e 99.
Funzioni matematiche speciali
L'header <math>
definisce alcune funzioni matematiche abbastanza comuni:
- trigonometriche:
sin
,cos
,tan
,asin
,acos
,atan
eatan2
; - iperboliche:
sinh
,cosh
,tanh
,asinh
,acosh
,atanh
; - esponenziali:
exp
,exp2
,frexp
,ldexp
,expm1
; - logaritmiche:
log10
,log2
,logb
,ilogb
,log1p
; - potenze:
pow
,sqrt
,cbrt
,hypot
; - speciali:
erf
,erfc
,tgamma
,lgamma
.
La proposta era di aggiungere nuove funzioni alla categoria ‘speciali' per colmare parecchie lacune che costringono ad utilizzare librerie non standardizzate, tuttavia tale cambiamento non è stato approvato per la versione finale del C++11. Chiaramente l'utilizzo di queste funzioni sarebbe stato limitato all'ambito ingegneristico ed alle discipline scientifiche.
La tabella seguente riporta le 23 funzioni approvate per lo standard C++11.
Nome della funzione | Prototipo della funzione | Espressione matematica |
---|---|---|
Polinomi associati di Laguerre | double assoc_laguerre(unsigned n, unsigned m, double x) ; | |
Polinomi associati di Legendre | double assoc_legendre(unsigned l, unsigned m, double x) ; | |
Funzione beta di Eulero | double beta(double x, double y) ; | |
Integrale ellittico completo di prima specie | double comp_ellint_1(double k) ; | |
Integrale ellittico completo di seconda specie | double comp_ellint_2(double k) ; | |
Integrale ellittico completo di terza specie | double comp_ellint_3(double k, double nu) ; | |
Funzione ipergeometrica confluente | double conf_hyperg(double a, double c, double x) ; | |
Funzione cilindrica di Bessel modificata regolarmente | double cyl_bessel_i(double nu, double x) ; | |
Funzione cilindrica di Bessel di prima specie | double cyl_bessel_j(double nu, double x) ; | |
Funzione cilindrica di Bessel modificata irregolarmente | double cyl_bessel_k(double nu, double x) ; | |
Funzione cilindrica di Neumann | double cyl_neumann(double nu, double x) ; | |
Integrale ellittico incompleto di prima specie | double ellint_1(double k, double phi) ; | |
Integrale ellittico incompleto di seconda specie | double ellint_2(double k, double phi) ; | |
Integrale ellittico incompleto di terza specie | double ellint_3(double k, double nu, double phi) ; | |
Integrale esponenziale | double expint(double x) ; | |
Polinomi di Hermite | double hermite(unsigned n, double x) ; | |
Serie ipergeometrica | double hyperg(double a, double b, double c, double x) ; | |
Polinomi di Laguerre | double laguerre(unsigned n, double x) ; | |
Polinomi di Legendre | double legendre(unsigned l, double x) ; | |
Funzione zeta di Riemann | double riemann_zeta(double x) ; | |
Funzione sferica di Bessel di prima specie | double sph_bessel(unsigned n, double x) ; | |
Funzione sferica associata di Legendre | double sph_legendre(unsigned l, unsigned m, double theta) ; | |
Funzione sferica di Neumann | double sph_neumann(unsigned n, double x) ; |
Ad ogni funzione è sufficiente aggiungere il suffisso ‘f' per ottenere la versione ‘float' ed il suffisso ‘l' per la versione ‘long double
'. Es:
float sph_neumann''f''( unsigned n, float x ) ; long double sph_neumann''l''( unsigned n, long double x ) ;
Wrapper reference
Il wrapper reference viene ottenuto da un'istanza della classe template reference_wrapper
nell'header <utility>
, il suo utilizzo è simile al riferimento ‘&' previsto dal linguaggio C++. Per ottenere un wrapper reference da un oggetto qualsiasi si utilizza la funzione template ref
(per un riferimento costante si usa cref
).
Il wrapper reference è utile soprattutto per le funzioni template quando vogliamo che ottengano un riferimento ai loro parametri invece di utilizzarne una copia:
// Questa funzione ottiene il parametro 'r' per riferimento e lo incrementerà.
void f( int &r ) { r++ ; }
// Funzione template.
template< class F, class P > void g( F f, P t ) { f(t) ; }
int main()
{
int i = 0 ;
g( f, i ) ; // Viene istanziata 'g< void ( int &r ), int >'
// quindi 'i' non viene modificato.
cout << i << endl ; // Output -> 0
g( f, ref(i) ) ; // Viene istanziata 'g<void(int r),reference_wrapper<int>>'
// quindi 'i' sarà modificato.
cout << i << endl ; // Output -> 1
}
Wrapper polimorfi per oggetti funzione
I wrapper polimorfi per oggetti funzione (Polymorphic Function Object Wrapper) sono simili ai puntatori a funzione per semantica e sintassi, ma il loro utilizzo è meno vincolato e possono riferirsi indistintamente a qualsiasi funzione che possa essere chiama con argomenti compatibili con quelli del wrapper.
Attraverso l'esempio seguente è possibile comprenderne le caratteristiche:
function<int ( int, int )> pf ; // Creazione del wrapper tramite la classe
// template 'function'.
plus<int> add ; // 'plus' è dichiarato come 'template<class T> T plus( T, T ) ;'
// quindi 'add' è di tipo 'int add( int x, int y )'.
pf = &add ; // L'assegnamento è corretto perché i
// parametri ed il tipo di ritorno corrispondono.
int a = pf( 1, 2 ) ; // NOTA: se il wrapper 'pf' non è riferito a nessuna
// funzione viene lanciata l'[[Eccezione (informatica)|eccezione]] 'bad_function_call'.
function<bool ( short, short )> pg ;
if( pg == nullptr ) // Sempre verificata perché 'pg' non
// è ancora assegnata a nessuna funzione.
{
bool adjacent( long x, long y ) ;
pg = &adjacent ; // I parametri ed il valore di ritorno sono compatibili,
// l'assegnamento è corretto.
struct prova
{
bool operator()( short x, short y ) ;
} car ;
pg = ref(car) ; // 'ref' è una funzione template che ritorna il wrapper
// della funzione membro 'operator()' di 'car'.
}
pf = pg ; // È corretto perché i parametri ed il valore di ritorno del
// wrapper 'pg' sono compatibili con quelli del wrapper 'pf'.
La classe template function
è definita all'interno dell'header <functional>
.
I type traits per la metaprogrammazione
La metaprogrammazione consiste nel creare un programma che crei o modifichi un altro programma (o se stesso). Questo può avvenire a tempo di compilazione oppure a tempo di esecuzione. Il comitato del C++ ha deciso di introdurre una libreria che consenta la metaprogrammazione a tempo di compilazione attraverso i template.
Ecco un ottimo esempio di quello che si può già ottenere, con lo standard attuale, attraverso la metaprogrammazione: una ricorsione di istanziazioni di template per il calcolo di una potenza.
template< int B, int N >
struct Pow
{
// Chiamata ricorsiva e ricombinazione.
enum{ value = B*Pow< B, N-1 >::value } ;
} ;
template< int B > struct Pow< B, 0 > // N == 0 condizione di terminazione.
{
enum{ value = 1 } ;
} ;
int tre_alla_quarta = Pow< 3, 4 >::value ;
Molti algoritmi possono essere utilizzati indistintamente per diversi tipi di dati, per questo motivo sono stati inseriti nello standard C++ i template, in modo da supportare la programmazione generica e rendere più compatto e gestibile il codice. Tuttavia capita spesso di imbattersi in algoritmi che necessitano di informazioni sui dati utilizzati. Queste informazioni possono essere ricavate durante l'istanziazione di una qualsiasi classe template utilizzando i type traits.
I type traits sono moltissimi e possono identificare la categoria di un oggetto e tutte le caratteristiche di una classe (o di una struttura). Sono definiti nel nuovo header <type_traits>
.
Nell'esempio seguente c'è la funzione template ‘elabora' che a seconda del tipo di dati inseriti istanzierà uno dei due algoritmi proposti (funzione.do_it
).
// Primo modo di operare.
template< bool B > struct funzione
{
template< class T1, class T2 > int do_it( T1 &, T2 & ) { /*...*/ }
} ;
// Secondo modo di operare.
template<> struct funzione<true>
{
template< class T1, class T2 > int do_it()( T1 *, T2 * ) { /*...*/ }
} ;
// Istanziando 'elabora' si instanzia automaticamente il modo di operare corretto.
template< class T1, class T2 > int elabora( T1 A, T2 B )
{
// Utilizza il secondo modo solo se 'T1' è un tipo intero e se 'T2' è
// un tipo in virgola mobile, altrimenti utilizza il primo modo.
return funzione< is_integral<T1> && is_floating_point<T2> >::do_it( A, B ) ;
}
Attraverso i type traits, definiti nell'header <type_transform>
, è possibile effettuare anche delle operazioni di trasformazioni sui tipi (lo static_cast
ed il const_cast
non sono sufficienti all'interno di un template).
Questo tipo di programmazione permette di ottenere un codice elegante e conciso; il vero punto debole di queste tecniche è il debugging: disagevole a tempo di compilazione e molto difficile durante l'esecuzione del programma.
Metodo Uniforme per determinare i tipi di ritorno di un oggetto funzione
Determinare (a tempo di compilazione) il tipo di ritorno di una funzione oggetto template, soprattutto se dipende dai parametri della funzione stessa, non è sempre intuitivo.
Prendiamo in esempio il codice seguente:
struct chiara
{
int operator()( int ) ; // Il tipo del parametro è
double operator()( double ) ; // uguale al tipo ritorno.
} ;
template< class Obj > class calcolo
{
public:
template< class Arg > Arg operator()( Arg& a ) const
{
return membro(a) ;
}
private:
Obj membro ;
} ;
Istanziando la classe template calcolo
, utilizzando come parametro la classe chiara
(ossia istanziando calcolo<chiara>
), la funzione oggetto di calcolo
avrà sempre lo stesso tipo di ritorno di quello della funzione oggetto di chiara
.
Se invece istanziamo la classe calcolo
utilizzando la classe confusa
(ossia istanziando calcolo<confusa>
):
struct confusa
{
double operator()( int ) ; // Il tipo del parametro
int operator()( double ) ; // NON è uguale al tipo ritorno.
} ;
Il tipo di ritorno della funzione oggetto di calcolo
non sarà lo stesso di quello della classe confusa
(potrà esserci una conversione da int
a double
o viceversa, a seconda dell'istanziazione di calculus<confused>.operator()
).
La nuova libreria, proposta per il prossimo standard, introduce la classe template result_of
, che permette al programmatore di determinare ed utilizzare il tipo di ritorno di una funzione oggetto per qualsiasi dichiarazione.
Nella versione corretta calcolo_ver2
viene impiegata la nuova utility per ricavare il tipo di ritorno della funzione oggetto:
template< class Obj >
class calcolo_ver2
{
public:
template< class Arg >
typename result_of<Obj(Arg)>::type operator()( Arg& a ) const
{
return membro(a) ;
}
private:
Obj membro ;
} ;
In questo modo nelle istanziazioni della funzione oggetto di ‘calcolo_ver2<confusa>
' non ci saranno più conversioni.
Il problema di determinare il tipo di ritorno di una chiamata ad un oggetto funzione è un sottoinsieme del problema più generale di determinare il tipo di risultato di un'espressione. Questo problema potrebbe essere risolto in futuro espandendo le funzionalità della typeof
a tutte le casistiche possibili.
Bibliografia
C++ Standards Committee Papers
- ISO/IEC DTR 19768 (19 ottobre, 2005) Doc No: N1905 Working Draft, Standard for programming Language C++
- ISO/IEC DTR 19768 (24 giugno, 2005) Doc No: N1836 Draft Technical Report on C++ Library Extensions
- Lawrence Crowl (2 maggio, 2005) Doc No: N1815 ISO C++ Strategic Plan for Multithreading
- Detlef Vollmann (24 giugno, 2005) Doc No: N1834 A Pleading for Reasonable Parallel Processing Support in C++
- Lawrence Crowl (25 agosto, 2005) Doc No: N1874 Thread-Local Storage
- Jan Kristoffersen (21 ottobre, 2002) Doc No: N1401 Atomic operations with multi-threaded environments
- Hans Boehm, Nick Maclaren (21 aprile, 2002) Doc No: N2016 Should volatile Acquire Atomicity and Thread Visibility Semantics?
- Lois Goldthwaite (2 febbraio, 2004) Doc No: N1592 Explicit Conversion Operators
- Francis Glassborow, Lois Goldthwaite (5 novembre, 2004) Doc No: N1717 explicit class and default definitions
- Bjarne Stroustrup, Gabriel Dos Reis (11 dicembre, 2005) Doc No: N1919 Initializer lists
- Herb Sutter, Francis Glassborow (6 aprile, 2006) Doc No: N1986 Delegating Constructors (revision 3)
- Michel Michaud, Michael Wong (6 ottobre, 2004) Doc No: N1898 Forwarding and inherited constructors
- Bronek Kozicki (9 settembre, 2004) Doc No: N1676 Non-member overloaded copy assignment operator
- R. Klarer, J. Maddock, B. Dawes, H. Hinnant (20 ottobre, 2004) Doc No: N1720 Proposal to Add Static Assertions to the Core Language (Revision 3)
- V Samko; J Willcock, J Järvi, D Gregor, A Lumsdaine (26 febbraio, 2006) Doc No: N1968 Lambda expressions and closures for C++
- J. Järvi, B. Stroustrup, D. Gregor, J. Siek, G. Dos Reis (12 settembre, 2004) Doc No: N1705 Decltype (and auto)
- B. Stroustrup, G. Dos Reis, Mat Marcus, Walter E. Brown, Herb Sutter (7 aprile, 2003) Doc No: N1449 Proposal to add template aliases to C++
- Douglas Gregor, Jaakko Järvi, Gary Powell (10 settembre, 2004) Doc No: N1704 Variadic Templates: Exploring the Design Space
- Gabriel Dos Reis, Bjarne Stroustrup (20 ottobre, 2005) Doc No: N1886 Specifying C++ concepts
- Daveed Vandevoorde (14 gennaio, 2005) Doc No: N1757 Right Angle Brackets (Revision 2)
- Walter E. Brown (18 ottobre, 2005) Doc No: N1891 Progress toward Opaque Typedefs for C++0X
- J. Stephen Adamczyk (29 aprile, 2005) Doc No: N1811 Adding the long long type to C++ (Revision 3)
- Chris Uzdavinis, Alisdair Meredith (29 agosto, 2005) Doc No: N1827 An Explicit Override Syntax for C++
- Herb Sutter, David E. Miller (21 ottobre, 2004) Doc No: N1719 Strongly Typed Enums (revision 1)
- Matthew Austern (9 aprile, 2003) Doc No: N1456 A Proposal to Add Hash Tables to the Standard Library (revision 4)
- Doug Gregor (8 novembre, 2002) Doc No: N1403 Proposal for adding tuple types into the standard library
- John Maddock (3 marzo, 2003) Doc No: N1429 A Proposal to add Regular Expression to the Standard Library
- P. Dimov, B. Dawes, G. Colvin (27 marzo, 2003) Doc No: N1450 A Proposal to Add General Purpose Smart Pointers to the Library Technical Report (Revision 1)
- Doug Gregor (22 ottobre, 2002) Doc No: N1402 A Proposal to add a Polymorphic Function Object Wrapper to the Standard Library
- D. Gregor, P. Dimov (9 aprile, 2003) Doc No: N1453 A proposal to add a reference wrapper to the standard library (revision 1)
- John Maddock (3 marzo, 2003) Doc No: N1424 A Proposal to add Type Traits to the Standard Library
- Daveed Vandevoorde (18 aprile, 2003) Doc No: N1471 Reflective Metaprogramming in C++
- Jens Maurer (10 aprile, 2003) Doc No: N1452 A Proposal to Add an Extensible Random Number Facility to the Standard Library (Revision 2)
- Walter E. Brown (28 ottobre, 2003) Doc No: N1542 A Proposal to Add Mathematical Special Functions to the C++ Standard Library (version 3)
- Douglas Gregor, P. Dimov (9 aprile, 2003) Doc No: N1454 A uniform method for computing function object return types (revision 1)
Articoli
- The C++ Source Bjarne Stroustrup (2 gennaio, 2006) A Brief Look at C++0x
- ^ C++11 - cppreference.com, su en.cppreference.com. URL consultato il 13 marzo 2020.
- ^ C++11 FAQ, su www2.research.att.com. URL consultato il 2 novembre 2010 (archiviato dall'url originale l'11 maggio 2011).
- ^ We Have FDIS! (Trip Report: March 2011 C++ Standards Meeting) « Sutter's Mill
- ^ We have an international standard: C++0x is unanimously approved « Sutter's Mill
- ^ ISO/IEC 14882:2011 - Information technology - Programming languages - C
- ^ Buy ISO/IEC 14882 ed3.0 - Information technology - Programming languages - C++ | IEC Webstore | Publication Abstract, Preview, Scope
- ^ When compilers, su www2.research.att.com. URL consultato il 2 novembre 2010 (archiviato dall'url originale l'11 maggio 2011).
- ^ C/C++ Users Journal Bjarne Stroustrup (maggio, 2005) The Design of C++0x: Reinforcing C++'s proven strengths, while moving into the future
- ^ Bjarne Stroustrup Expounds on Concepts and the Future of C, su devx.com. URL consultato il 10 settembre 2011 (archiviato dall'url originale il 7 gennaio 2012).
- 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
- (EN) Sito ufficiale, su iso.org.
- (EN) The C++ Standards Committee, su open-std.org.
- (EN) Bjarne Stroustrup's homepage, su research.att.com.
- (EN) C++0X: The New Face of Standard C++, su informit.com. URL consultato il 19 settembre 2006 (archiviato dall'url originale l'8 maggio 2006).
- Installare C++11 su windows, su gameprog.it. URL consultato il 12 luglio 2013 (archiviato dall'url originale il 19 agosto 2013).