SlideShare a Scribd company logo
1 of 150
Download to read offline
Alberto Barbati
alberto@gamecentric.com
Introduzione al C++ Moderno
Commit University
9 Aprile 2019, Firenze
Alberto Barbati
Programmatore C++ entusiasta dal
1990
Nella game industry dal 2000
Sviluppatore e formatore
Core Trainer di Game Programming
presso Digital Bros Game Academy
Segue i lavori della C++ Committee dal
2008
Commit University – 9 Aprile 2019, Firenze
Cosa vedremo oggi?
• Brevissima storia del C++
• Le principali novità introdotte nel ciclo C++11/14/17
• Di queste, vedremo, nel dettaglio:
• Move semantic
• Smart pointer
• Lambda expression
• Un’anteprima di quello che ci aspetta nel C++20
Commit University – 9 Aprile 2019, Firenze
Brevissima storia del C++
Inizi del C++ Se vogliamo scegliere una data di
nascita al linguaggio C++, allora
sicuramente dobbiamo andare
indietro fino al 1985, quando
Bjarne Stroustrup pubblica il libro
«The C++ Programming
Language»
Commit University – 9 Aprile 2019, Firenze
1998: uno standard internazionale
• Il linguaggio subisce varie revisioni, ma viene presto percepito il
rischio della creazione di dialetti non compatibili
• Per questo motivo, nel 1998 un gruppo di lavoro in seno
all’organizzazione ISO, pubblica la prima versione «Standard» del
linguaggio, chiamata a posteriori C++98
• La costituita C++ Committee pubblica quindi successivamente una
seconda revisione dello Standard nel 2003, chiamata C++03
Commit University – 9 Aprile 2019, Firenze
C++ «Classico»
• Il C++03 propone:
• Supporto per la programmazione a oggetti, tramite i concetti di
classi, ereditarietà e polimorfismo
• Supporto per la programmazione generica con template
• Overloading degli operatori
• Una libreria standard (la cosiddetta STL) basata sui template
Commit University – 9 Aprile 2019, Firenze
Il C++ si diffonde
• Il C++03 inizia ad essere adottato e riscuote un moderato successo
in alcuni ambiti, principalmente come alternativa al linguaggio C da
cui trae ispirazione
• È anche soggetto ad aspre critiche per la sua percepita complessità
e i problemi delle sue implementazioni e in particolare della STL
• Il lungo periodo fino al 2011 è cruciale, perché c’è aspra concorrenza
da parte di altri linguaggi e la C++ Committee fatica a trovare un
accordo su come far progredire il linguaggio
Commit University – 9 Aprile 2019, Firenze
2011: C++ «Moderno»
• Nel 2011 viene finalmente pubblicato un nuovo standard, il C++11
• Nel modifiche introdotte sono di tale portata da segnare una netta
cesura rispetto al C++03 e iniziare l’era del C++ Moderno
• La commissione inoltre si impegna a fornire nuove revisioni del
linguaggio con cadenza triennale, per mantenere alta l’attenzione e
evitare di alienarsi la comunità dei programmatori come stava per
accadere nel periodo 2003-2011
• Da allora sono stati pubblicati C++14 e C++17
Commit University – 9 Aprile 2019, Firenze
Dimensione dello Standard C++ in pagine
Commit University – 9 Aprile 2019, Firenze
C++03
900
C++11
1300
C++14
1400
C++17
1600
C++20
1800
Commit University – 9 Aprile 2019, Firenze
Novità del C++ moderno
Cosa rende il C++11 «moderno»?
• Le principali innovazioni del C++11 sono:
• Un nuovo object model che introduce il concetto di move-
semantic
• Il potenziamento dei template che apre la porta alla meta-
programmazione
• Auto, constexpr, lambda expression, range-for e attributi
• Nuove librerie che includono molti vocabulary type
• Supporto per il multi-threading
Commit University – 9 Aprile 2019, Firenze
Stile di programmazione moderno
• Le modifiche al linguaggio hanno un profondo effetto sullo stile di
programmazione:
• Gestione degli oggetti sull’heap tramite contenitori e smart
pointer, drastica riduzione dell’uso di puntatori, new, delete, ecc.
• Uso dei vocabulary types per rendere il codice più espressivo
• Aumento del codice eseguito a compile-time (constexpr e meta-
programmazione)
• Spostamento dell’enfasi dal polimorfismo dinamico (funzioni
virtuali) al polimorfismo statico (template)
Commit University – 9 Aprile 2019, Firenze
Risorse Utili C++
• isocpp.org: sito ufficiale della C++ Committee
• cppreference.com: la «wikipedia» del C++11/14/17/20
• github.com/isocpp/CppCoreGuidelines: Core Guidelines del C++
• github.com/cplusplus/draft: sorgenti LaTeX dello Standard C++
• godbolt.org: compilatore online di C++
Commit University – 9 Aprile 2019, Firenze
Novità del linguaggio
Commit University – 9 Aprile 2019, Firenze
auto i = 0; // i è un int
auto f = 0.0; / f è un double
// C++03
std::vector<int>::iterator it =
std::find(vec.begin(), vec.end(), 0);
// C++11
auto it =
std::find(vec.begin(), vec.end(), 0);
auto
La parola chiave auto, utilizzata nelle
prime versioni del C++ e deprecata in
C++03 per inutilizzo, è stata riabilitata
in C++11 con un nuovo significato
Consente di dichiarare una variabile
deducendo il tipo dall’espressione
usata per inizializzarla
Rende il codice molto più conciso, in
particolare quando ci sono di mezzo i
template
Commit University – 9 Aprile 2019, Firenze
auto func() // ritorna int
{
return 0;
}
// il valore di ritorno è il tipo
// dell’espressione a * b
template <class T1, class T2>
auto mul(T1 a, T2 b)
{
return a * b;
}
auto
La parola chiave auto può anche
essere usata per dedurre il valore di
ritorno di una funzione
Questo può essere utile nel codice
generico, quando il tipo non è noto a
priori
Commit University – 9 Aprile 2019, Firenze
Linee guida: auto
• Il consiglio dei guru è «almost always auto» ovvero utilizzare auto
praticamente ovunque, quando possibile
• C’è da evidenziare una certa riluttanza della community ad applicare
questa linea guida, che è dovuta spesso a preconcetti sulla presunta
minore leggibilità del codice e sulla perdita di controllo sui tipi
• Dopo anni di programmazione C++11, la mia personale esperienza è
che questi dubbi sono infondati
Commit University – 9 Aprile 2019, Firenze
// C++03/11
int array[3] = { 1, 2, 3 };
string s("hello, world");
// C++11
int array[3] { 1, 2, 3 };
string s {"hello, world"};
Inizializzazione
«uniforme»
Viene introdotta una nuova sintassi di
inizializzazione per uniformare
l’inizializzazione di oggetti di classe e
aggregati
In sostanza sostituisce le parentesi
tonde () con le graffe {}
Risolve alcuni problemi di parsing
(most vexing parse) e permette di
avere una sintassi per
l’inizializzazione di default
Commit University – 9 Aprile 2019, Firenze
Most vexing parse e inizializzazione
Commit University – 9 Aprile 2019, Firenze
• Inizializza una stringa vuotastd::string s;
• Inizializza una stringa vuotastd::string s("");
• Dichiara una funzione s che non accetta parametri!std::string s();
• Inizializza una stringa vuotastd::string s{};
• Inizializza una stringa vuotastd::string s{""};
Most vexing parse e inizializzazione
Commit University – 9 Aprile 2019, Firenze
• La variabile non viene inizializzata!int i;
• Dichiara una funzione i che non accetta parametri!int i();
• Inizializza i al valore 0int i{};
Inizializzazione uniforme in altri contesti
• L’inizializzazione uniforme si può usare in molti altri contesti
• Conversione di tipo:
Tipo(argomenti) ➔ Tipo{argomenti}
• Questo risolve un annoso problema… non tutti sanno che:
• Tipo(a, b) invoca un costruttore con due parametri, se c’è
• Tipo(a) è equivalente a (Tipo)a che è un C-style cast! Potrebbe
invocare il costruttore con un parametro, ma anche no
• La sintassi Tipo{a} invoca sempre e solo il costruttore
Commit University – 9 Aprile 2019, Firenze
Inizializzazione uniforme in altri contesti
• In molti casi, il tipo può essere dedotto dal contesto:
• func({ argomenti }) deduce il tipo dal tipo del parametro
• return { argomenti }; deduce il tipo dal tipo di ritorno
• Costruttori:
• MyClass(argomenti) : x { expr }
{ … }
• L’inizializzazione uniforme è più sicura, perché evita il narrowing:
• int y { x }; // ok solo se x è rappresentabile in int
Commit University – 9 Aprile 2019, Firenze
Inizializzazione di lista
• Nuova feature e piccolo gotcha: la sintassi uniforme consente la
cosiddetta list-initialization, che permette di passare al costruttore
una lista di lunghezza variabile di oggetti
• È utilizzata prevalentemente per i contenitori:
• std::vector<int> vec { 1, 2, 3, 4, 5 };
• Attenzione però!
• std::vector<int> vec { 5 }; // vec è { 5 }
• std::vector<int> vec ( 5 ); // vec è { 0, 0, 0, 0, 0 }
Commit University – 9 Aprile 2019, Firenze
Linee guida: inizializzazione uniforme
• La linea guida è:
• Preferire la sintassi di inizializzazione uniforme {} alla sintassi
«tradizionale» con ()
• L’unica eccezione è per i tipi che consentono la list-initialization
per cui le due sintassi hanno effetti differenti
• Nei tipi definiti dall’utente, evitare di introdurre ambiguità tra la
list-initialization e gli altri costruttori
Commit University – 9 Aprile 2019, Firenze
// C++03
for (std::vector<int>::iterator it =
vec.begin(); it != vec.end(); ++it)
{ /* ... */}
// C++11
for (int i : vec)
{ /* ... */}
for each
Viene introdotta una nuova sintassi
compatta per iterare tutti gli elementi
di un contenitore
La sintassi funziona con gli array, tutti
i contenitori della libreria standard ed
è estendibile anche per i tipi definiti
dall’utente
Commit University – 9 Aprile 2019, Firenze
template <class... Args>
class MyClass {};
MyClass<int, long> x;
MyClass<float, double> y;
template <class... Args>
void fun(Args... Args);
fun(0, 0l);
fun("", 0, 3.14);
Template variadici
Diventa possibile avere un template
con un numero variabile di parametri
Questo vale sia per i template di classi
che per quelli di funzione
Commit University – 9 Aprile 2019, Firenze
struct TypeWithLongName;
// C++03
typedef TypeWithLongName Type;
typedef void (*Func)(int, double);
// C++11
using Type = TypeWithLongName;
using Func = void (*)(int, double);
template <class T>
using MyVector
= std::vector<T, MyAllocator<T>>;
MyVector<int> v;
using
La parola using può essere usata
per introdurre alias di tipi
Si ottiene lo stesso effetto di
typedef, ma la sintassi è molto più
semplice e leggibile
Inoltre, con using si può definire un
template di alias
Commit University – 9 Aprile 2019, Firenze
struct Base
{
virtual void f();
virtual void g(int);
virtual void h();
};
struct Derived : Base
{
void f() override; // ok
void g() override; // errore!
void h() final;
};
struct MostDerived : Derived
{
void h(); // errore!
};
final e override
Vengono aggiunge due parole chiave
contestuali, final e override per
un maggior controllo del
polimorfismo
override richiede al compilatore di
verificare che esista una funzione
virtuale corrispondente
final impedisce l’override di una
funzione virtuale. Se usata sull’intera
classe, impedisce la derivazione
Commit University – 9 Aprile 2019, Firenze
int f();
// i viene inizializzata a runtime
static int i { f() };
// errore! f() non può essere
// eseguita a compile time
constexpr int j { f() };
// k è inizializzata a compile time
constexpr int g() { /* ... */ }
constexpr int k { g() };
constexpr
Viene aggiunta una nuova parola
chiave constexpr per:
• Forzare l’inizializzazione di una
variabile a compile time
• Indicare funzioni che possono
essere eseguite a compile time
Commit University – 9 Aprile 2019, Firenze
Novità della libreria standard
Commit University – 9 Aprile 2019, Firenze
// contiene un int, un float e
// un long
std::tuple<int, float, long> t;
// contiene un int oppure un float
// oppure un long (C++17)
std::variant<int, float, long> v;
// contiene qualsiasi tipo (C++17)
std::any a;
// contiene un int oppure no (C++17)
std::optional<int> o;
Tipi vocabolario
La libreria introduce molti vocabulary
type per rendere il codice più
espressivo
Commit University – 9 Aprile 2019, Firenze
Nuovi contenitori
• Vengono introdotti nuovi contenitori:
• array: array di lunghezza fissa (pur ricalcando di fatto la
funzionalità dei normali array C, offre alcuni vantaggi)
• forward_list: lista linkata a link singolo
• unordered_set, unordered_multiset, unordered_map e
unordered_multimap: versioni hashed dei contenitori associativi
Commit University – 9 Aprile 2019, Firenze
Supporto per il multithread
Commit University – 9 Aprile 2019, Firenze
• La libreria offre le seguenti funzionalità
• Una classe thread per lanciare e eseguire thread
• Classi per gestire gli accessi atomici alla memoria
• Classi per mutex, lock e condition variables
• Classi per la gestione della concorrenza tramite future
std::future
int calcolo();
int main()
{
std::future<int> fut = std::async(calcolo);
/* il codice non è bloccato, l’esecuzione può proseguire */
int i = fut.get(); // bloccante: attende il termine del
// calcolo, se necessario
/* ... */
}
Commit University – 9 Aprile 2019, Firenze
E tanto altro…
• Smart pointer (che vedremo nella sezione dedicata)
• Regular expression
• error_code e error_condition per astrarre i codici di errore
• (C++17) Allocatori polimorfici (memory resource)
• (C++17) path per astrarre l’accesso al file system
• (C++17) Algoritmi paralleli
• Ecc.
Commit University – 9 Aprile 2019, Firenze
Commit University – 9 Aprile 2019, Firenze
Move semantic
Oggetto/Valore
• Consideriamo la seguente variabile:
int var = 42;
• La variabile è memorizzata un una regione di «memoria», che sia
sullo stack, heap o in un registro, è irrilevante
• Il suo «valore» è unicamente determinato dalla pattern di bit
contenuta nella regione
• Copiare il valore della variabile, significa banalmente, copiare la
pattern di bit in un’altra regione
Commit University – 9 Aprile 2019, Firenze
Tipi complessi
• Poiché in C++ possiamo definire nuovi tipi, è facile pensare a tipi più
complessi per cui la definizione di «valore» data nella slide
precedente è limitante
• Pensate, ad esempio ad un contenitore di oggetti o a una stringa
• In questi casi, si potrebbe dire che il «valore» non sia determinato
unicamente dai byte della regione in cui è memorizzato…
Commit University – 9 Aprile 2019, Firenze
Il caso di std::vector
• L’oggetto di libreria std::vector è un contenitore in grado di gestire una
sequenza arbitraria di altri oggetti, il cui numero è limitato unicamente
dalla memoria disponibile e può variare durante l’esecuzione
• Eppure la dimensione in memoria di un oggetto std::vector è fissa
• L’implementazione tipica di std::vector consiste in:
• Un puntatore ad una zona di memoria allocata sul heap per
memorizzare la sequenza degli oggetti «contenuti»
• Un contatore degli oggetti contenuti
• E poco altro…
Commit University – 9 Aprile 2019, Firenze
Costruire un vettore
• Considerate questo codice:
std::vector<int> vec1 { 1, 2, 3, 4 };
• Questa riga costruisce un vettore di interi e lo inizializza con una
sequenza di quattro interi
• Viene chiamato uno dei costruttori di std::vector che, tra le altre
cose, alloca sulla heap memoria sufficiente per i quattro interi
Commit University – 9 Aprile 2019, Firenze
Distruggere un vettore
• Diciamo che vec1 ha la custodia della memoria allocata, in quanto ha
la responsabilità di rilasciarla, quando non servirà più
• Quando la variabile vec1 esce di scope, viene chiamato il distruttore
di std::vector che rilascia la memoria e la custodia termina
Commit University – 9 Aprile 2019, Firenze
Copiare un vettore
• Cosa succede se copio vec1?
std::vector<int> vec2 = vec1;
• Se il C++ si limitasse a copiare la pattern di bit di vec1 in vec2, come
un normale intero, farebbe un pessimo servizio!
• Infatti il risultato sarebbe quello di avere due variabili che fanno
riferimento alla stessa zona di memoria sullo heap e quindi non
potremmo più distinguere quale delle due ha la custodia della
memoria!
Commit University – 9 Aprile 2019, Firenze
Costruttore di copia
• In C++03, sappiamo che in questo caso viene invocato uno speciale
costruttore di std::vector chiamato costruttore di copia che alloca
memoria per altri 4 interi e copia la sequenza
• Il risultato che è otteniamo due vettori che contengono copie degli
stessi oggetti
• Ciascun vettore ha la custodia della propria zona di memoria e non
c’è ambiguità su chi deve rilasciare cosa
Commit University – 9 Aprile 2019, Firenze
Spreco
std::vector<int> makeVector()
{
std::vector<int> vec;
/* popola v con valori */
return vec;
}
Commit University – 9 Aprile 2019, Firenze
• Il fatto che l’intera sequenza venga copiata è a volte motivo di spreco
• Considerate la seguente funzione
Workaround
void makeVector(std::vector<int>& v)
{
/* popola v con valori */
}
Commit University – 9 Aprile 2019, Firenze
• L’istruzione return fa una copia il vettore contenuto nella variabile locale
vec nel contesto del chiamante della funzione, quindi distrugge vec!
• Questo è il motivo per cui molte linee guida di programmazione C++03
rifiutano di scrivere funzioni in quel modo, preferendo lo stile più
efficiente, ma innaturale e più scomodo da usare:
Il costruttore di move
• Il C++11 introduce un nuovo tipo di costruttore, il costruttore di
spostamento o move, che implementa la semantica di «spostare il
valore»
• Utilizzando un siffatto costruttore, il vettore locale vec nell’esempio
può passare la custodia al contesto del chiamante ed evitare l’inutile
doppia allocazione
• La prima stesura della funzione makeVector, che è più naturale,
improvvisamente diventa efficiente come l’altra!
Commit University – 9 Aprile 2019, Firenze
Copia vs. Move
• La differenza fondamentale tra una copia e uno spostamento è che
nel caso della copia l’oggetto «sorgente» rimane invariato, mentre
nel caso dello spostamento l’oggetto che fornisce il valore da
spostare deve essere modificato, tanto quanto l’oggetto che riceve il
valore
Commit University – 9 Aprile 2019, Firenze
Operazione di copia
Commit University – 9 Aprile 2019, Firenze
?
Destinazione
{1, 2, 3, 4}
Sorgente
Operazione di copia
Commit University – 9 Aprile 2019, Firenze
{1, 2, 3, 4}
Destinazione
{1, 2, 3, 4}
Sorgente
L’oggetto sorgente non
viene modificato
Operazione di spostamento
Commit University – 9 Aprile 2019, Firenze
?
Destinazione
{1, 2, 3, 4}
Sorgente
Operazione di spostamento
Commit University – 9 Aprile 2019, Firenze
{1, 2, 3, 4}
Destinazione
{}
Sorgente
L’oggetto sorgente
viene modificato!
Sintassi di move?
• Il C++ deliberatamente non introduce un nuovo operatore per lo
spostamento
• Lo spostamento può avvenire a seguito di una costruzione (costruttore di
move) oppure di un assegnamento (move-assignement)
• Ad esempio, data l’espressione di assegnamento:
expr1 = expr2;
il compilatore sceglierà automaticamente l’operazione giusta da fare
• Come fa?
Commit University – 9 Aprile 2019, Firenze
Categorie di espressioni
• Per ottenere questo risultato, il linguaggio C++ suddivide tutte le
possibili espressioni in tre categorie principali, più due
raggruppamenti
• I due criteri fondamentali sono:
• L’espressione rappresenta l’identità di un oggetto o no?
• L’espressione è «disponibile» per l’operazione di move o no?
Commit University – 9 Aprile 2019, Firenze
Categorie di espressioni
Espressioni che
determinano l’identità di
oggetti
Espressioni non legate
all’identità di oggetti
Espressioni che NON
consentono l’operazione di
spostamento
Espressioni che
consentono l’operazione di
spostamento
Commit University – 9 Aprile 2019, Firenze
Categorie di espressioni
Espressioni che
determinano l’identità di
oggetti
Espressioni non legate
all’identità di oggetti
Espressioni che NON
consentono l’operazione di
spostamento
vec1
index
array[2]
obj.member
Espressioni che
consentono l’operazione di
spostamento
Commit University – 9 Aprile 2019, Firenze
Categorie di espressioni
Espressioni che
determinano l’identità di
oggetti
Espressioni non legate
all’identità di oggetti
Espressioni che NON
consentono l’operazione di
spostamento
vec1
index
array[2]
obj.member
Espressioni che
consentono l’operazione di
spostamento
3.14156265
index + 42
makeVector()
Commit University – 9 Aprile 2019, Firenze
Categorie di espressioni
Espressioni che
determinano l’identità di
oggetti
Espressioni non legate
all’identità di oggetti
Espressioni che NON
consentono l’operazione di
spostamento
vec1
index
array[2]
obj.member
Espressioni che
consentono l’operazione di
spostamento
3.14156265
index + 42
makeVector()
Commit University – 9 Aprile 2019, Firenze
std::move
• Per comunicare al compilatore la mia intenzione di rendere una
variabile disponibile per lo spostamento uso la funzione di libreria
apposita:
std::move(vec2)
• Utilizzando questa espressione a destra dell’operatore = si innesca
l’operazione di spostamento
vec1 = std::move(vec2);
• Importante: non è la funzione std::move che fa lo spostamento,
ma è l’operatore =!
Commit University – 9 Aprile 2019, Firenze
Categorie di espressioni
Espressioni che
determinano l’identità di
oggetti
Espressioni non legate
all’identità di oggetti
Espressioni che NON
consentono l’operazione di
spostamento
vec1
*ptr
array[2]
obj.member
Espressioni che
consentono l’operazione di
spostamento
std::move(vec1)
std::move(obj.member)
std::move(obj).member
3.14156265
index + 42
makeVector()
Commit University – 9 Aprile 2019, Firenze
Espressioni che
consentono l’operazione
di spostamento
rvalue
Espressioni che
determinano l’identità
di un oggetto
glvalue
Recap: categorie di espressioni
Commit University – 9 Aprile 2019, Firenze
identità sì
move no
lvalue
identità sì
move sì
xvalue
identità no
move sì
prvalue
Due cose su std::move
• std::move non è una funzione «magica»
• È solo il modo più semplice e conciso di trasformare un lvalue in un
xvalue e quello che si usa nell’uso comune
• Ci sono altre espressioni che appartengono alla categoria xvalue, ad
esempio le chiamate a una funzione il cui valore di ritorno è una
rvalue reference
Commit University – 9 Aprile 2019, Firenze
Costruttore di copia
• Il tipico costruttore di copia, usato fin da C++98 ha questa forma:
MyClass(const MyClass& x);
• Il carattere & indica che x è un riferimento: il parametro x della funzione
viene legato all’argomento con cui la funzione viene chiamata
• In assenza del const, il riferimento non può legarsi a espressioni di
categoria rvalue e per questo si chiama lvalue reference
• La presenza di const invece permette al riferimento di legarsi a qualsiasi
categoria di espressioni, in quanto il const garantisce che l’oggetto non
verrà modificato
Commit University – 9 Aprile 2019, Firenze
Rvalue reference e costruttore di move
• Il C++11 introduce il concetto di rvalue reference, ovvero un nuovo
tipo riferimento che però lega unicamente con espressioni di
categoria rvalue a mai con gli lvalue
• Il costruttore di move prende quindi questa forma:
MyClass(MyClass&& x);
• A questo punto sarà il meccanismo di overloading a decidere quale
costruttore utilizzare
Commit University – 9 Aprile 2019, Firenze
Overloading copy/move
class MyClass
{
public:
// copy ctor
MyClass(const MyClass& x);
// move ctor
MyClass(MyClass&& x);
/* ... */
};
MyClass makeClass();
Commit University – 9 Aprile 2019, Firenze
MyClass c1;
// lvalue -> copy ctor
MyClass c2 { c1 };
// xvalue -> move ctor
MyClass c3 { std::move(c1) };
// prvalue -> move ctor
MyClass c4 { makeClass() }
= delete
struct MoveOnly
{
~MoveOnly() {}
// costruttore di move e move assignment
MoveOnly(MoveOnly&&);
MoveOnly& operator=(MoveOnly&&);
// niente costruttore di copia e copy assignment
MoveOnly(const MoveOnly&) = delete;
MoveOnly& operator=(const MoveOnly&) = delete;
};
Commit University – 9 Aprile 2019, Firenze
• È possibile sopprimere una funzione dichiarandola con = delete
Un caso speciale, l’istruzione return
• Torniamo all’esempio da cui siamo partiti, ovvero:
std::vector<int> makeVector()
{
std::vector<int> vec;
/* popola v con valori */
return vec; // vec è un lvalue!
}
• Questo è l’unico caso in cui al compilatore è concesso di convertire
implicitamente da lvalue a xvalue, quindi non è necessario usare
std::move, verrà usato lo spostamento comunque se possibile
Commit University – 9 Aprile 2019, Firenze
Impatto della move semantic
• A prima vista, sembra che l’effetto della move semantic si limiti ad
una ottimizzazione, mediante l’eliminazione di copie ridondanti
• In verità ha effetti più profondi sullo stile di programmazione:
• Consente interfacce più naturali, evitando il ricorso ai parametri
«in sola uscita»
• Consente l’introduzione di tipi move-only per rappresentare in
maniera espressiva quelle risorse che, dal punto di vista
semantico, non possono essere copiate
Commit University – 9 Aprile 2019, Firenze
Smart Pointer
Commit University – 9 Aprile 2019, Firenze
Smart pointer
• Gli smart pointer sono template di classi che consentono di:
• Liberarsi dalla schiavitù del new/delete (in particolare, del delete)
• Scrivere più facilmente codice privo di memory-leak, in particolare
se si usano le eccezioni
• Scrivere codice più “espressivo” che consenta non solo al
compilatore, ma anche all’umano di capire al volo l’intento del
programmatore
• Implementare tecniche particolari, ad es. l’idioma “pimpl”
Commit University – 9 Aprile 2019, Firenze
Custodia
• Come per l’esempio dei vettori visto prima, ogni volta che viene allocata
memoria dinamicamente sorge il problema della custodia, ovvero: chi si
prende la responsabilità di pulire quando abbiamo finito?
• Utilizzando i puntatori “nudi” alla C, il compilatore non ha alcuna
informazione sulla custodia e quindi tutto è a carico del programmatore
che deve tenerne conto e documentarla
• Gli smart pointers invece incapsulano il concetto di custodia e questo
consente al compilatore di generare il codice corretto automaticamente
Commit University – 9 Aprile 2019, Firenze
std::unique_ptr<T>
• unique_ptr rappresenta un puntatore con custodia esclusiva: sono
io e solo io che ho la responsabilità di deallocare
• Uno unique_ptr non può essere copiato, altrimenti la custodia non
sarebbe più esclusiva!
• Però può essere spostato, in quel caso la custodia si sposta assieme
al puntatore
• Ecco uno dei tipi move-only di cui parlavamo prima!
Commit University – 9 Aprile 2019, Firenze
Metodi di std::unique_ptr<T>
• Se ptr è un unique_ptr<T> le operazioni più comuni sono:
*ptr // dereferenzia il puntatore
ptr->m // dereferenzia il puntatore
if (ptr) // controlla se il puntatore è nullo
ptr.get() // ottiene il puntatore nudo
ptr.reset(); // rilascia la memoria e azzera
ptr.release(); // come get() + reset()
Commit University – 9 Aprile 2019, Firenze
Piccolo quiz!
• Dati due std::unique_ptr, ptr1 e ptr2 questa istruzione:
ptr1 = ptr2;
causa un errore di compilazione, perché unique_ptr è un tipo move-only!
• Se invece scrivo così:
ptr1 = std::move(ptr2);
allora compila correttamente e l’effetto è di:
• Rilasciare la memoria gestita da ptr1
• Passare la custodia della memoria di ptr2 in ptr1
• Azzerare ptr2
Commit University – 9 Aprile 2019, Firenze
std::unique_ptr<T, D>
• Per default, il distruttore di unique_ptr usa sempre l’operatore
delete per deallocare il puntatore, quindi funziona solo se il
puntatore sia stato ottenuto con una chiamata a new
• Se questo non è il vostro caso, potete specificare un custom deleter,
che viene invocato al posto di delete
• Il deleter viene passato come argomento template e quindi fa parte
del tipo del puntatore
Commit University – 9 Aprile 2019, Firenze
Piccola parentesi: nullptr
• In C++11 viene introdotto nullptr, che agisce come puntatore nullo
“universale” in sostituzione della macro NULL ereditata dal C
• L’espressione nullptr ha il tipo a sè stante std::nullptr_t che
può essere convertito a qualsiasi puntatore, questo rende più
semplice l’interazione con i template
• Perciò possiamo scrivere, indipendentemente dal tipo di puntatore
ptr = nullptr;
Commit University – 9 Aprile 2019, Firenze
std::shared_ptr
• shared_ptr rappresenta un puntatore con custodia condivisa
• Quando viene copiato, la custodia viene condivisa tra le due copie
• L’oggetto puntato viene deallocato solo quando l’ultimo oggetto che
ha la custodia viene distrutto
• L’effetto è ottenuto tramite la tecnica del reference count
Commit University – 9 Aprile 2019, Firenze
Metodi di std:: shared_ptr<T>
• Se ptr è un shared_ptr<T> le operazioni più comuni sono:
*ptr // dereferenzia il puntatore
ptr->m // dereferenzia il puntatore
if (ptr) // controlla se il puntatore è nullo
ptr.get() // ottiene il puntatore nudo
ptr.reset(); // rilascia la custodia e azzera
• Cioè, praticamente, le stesse di unique_ptr con l’eccezione di
release che non ha senso per uno shared_ptr
Commit University – 9 Aprile 2019, Firenze
Altro piccolo quiz
ptr1 = ptr2;
• Compila, l’effetto è:
• Rilascia la custodia di ptr1
• Assegna a ptr1 una custodia della
risorsa gestita da ptr2 condivisa con
ptr2
Commit University – 9 Aprile 2019, Firenze
ptr1 = std::move(ptr2);
• Compila, l’effetto è:
• Rilascia la custodia di ptr1
• Assegna a ptr1 la custodia della risorsa
gestita da ptr2
• Azzera ptr2
• Dati due std::shared_ptr, ptr1 e ptr2
Custom deleter
• Anche nel caso di shared_ptr, il distruttore per default chiama
delete, ma è possibile specificare un custom deleter
• Al contrario di unique_ptr, però, il custom deleter non fa parte del
tipo e viene memorizzato nel puntatore stesso con la tecnica del
type erasing
• Questo consente l’utilizzo di tipi opachi e il cosidetto idioma pimpl
Commit University – 9 Aprile 2019, Firenze
std::weak_ptr
• weak_ptr è un osservatore di shared_ptr, ovvero mantiene un
riferimento ad un oggetto gestito tramite shared_ptr ma non
partecipa alla custodia
• Dato un weak_ptr è possibile sapere se l’oggetto è ancora
disponibile o se è stato rilasciato
• Se è ancora disponibile, si può convertire un weak_ptr in uno
shared_ptr per ottenere la custodia dell’oggetto
Commit University – 9 Aprile 2019, Firenze
Uso degli smart pointer
void fun()
{
std::unique_ptr<Type> ptr { new Type() };
// non è necessario alcun delete
}
Commit University – 9 Aprile 2019, Firenze
• Gli smart pointer semplificano il codice perché ci liberano dalla necessità del
delete, anche in presenza di eccezioni
Uso degli smart pointer
void fun()
{
// make_unique evita persino il new
auto ptr = std::make_unique<Type>();
// non è necessario alcun delete
}
Commit University – 9 Aprile 2019, Firenze
• Da C++14, possiamo addirittura fare a meno del new
Uso degli smart pointer
class MyClass
{
std::unique_ptr<Type> ptr;
public:
// non è necessario scrivere un distruttore esplicito
// perché il distruttore di unique_ptr si occupa di tutto
}
Commit University – 9 Aprile 2019, Firenze
• Possiamo evitare di scrivere distruttori
Smart pointer e contenitori
// vettore di puntatori a custodia unica
std::vector<std::unique_ptr<Type>> vec_unique;
// vettore di puntatori a custodia condivisa
std::vector<std::shared_ptr<Type>> vec_shared;
Commit University – 9 Aprile 2019, Firenze
• Possiamo mettere in sicurezza puntatori nei nostri contenitori, certi che la
memoria verrà deallocata correttamente
Interfacce e smart pointer
// ma chi dealloca???
MyType* createObject();
Commit University – 9 Aprile 2019, Firenze
• Con gli smart pointer, è possibile scrivere interfacce più espressive
Interfacce e smart pointer
// ma chi dealloca???
MyType* createObject();
Commit University – 9 Aprile 2019, Firenze
// dealloca il chiamante
std::unique_ptr<MyType> createObject();
// la custodia è condivisa
std::shared_ptr<MyType> createObject();
• Con gli smart pointer, è possibile scrivere interfacce più espressive
Oggetti funzione e espressioni lambda
Commit University – 9 Aprile 2019, Firenze
Passare “pezzi di codice”
• Vi sarà sicuramente capitata l’esigenza di passare come parametro
di una funzione un «pezzo di codice», per esempio:
• Usando un algoritmo della libreria standard
• Usando una funzione che richiede una callback
• ecc. …
Commit University – 9 Aprile 2019, Firenze
Puntatori a funzione
void increment(int& i) { ++i; }
void f(std::vector<int>& v)
{
std::for_each(v.begin(), v.end(), increment);
}
Commit University – 9 Aprile 2019, Firenze
Function object
struct Increment
{
void operator()(int& i) { ++i; }
};
void f(std::vector<int>& v)
{
std::for_each(v.begin(), v.end(), Increment{});
}
Commit University – 9 Aprile 2019, Firenze
Function object
struct IsEven
{
bool operator()(int i) { return i % 2 == 0; }
};
void f(std::vector<int>& v)
{
auto it = std::find_if(v.begin(), v.end(), IsEven{});
}
Commit University – 9 Aprile 2019, Firenze
Function object vs. puntatori a funzione
• Il principale vantaggio di un function rispetto ad un puntatore a
funzione è che il function object è un tipo
• Questo significa che:
• Partecipa al meccanismo dei template
• Consente la sostituzione inline della funzione
• Consente la creazione di oggetti stateful
Commit University – 9 Aprile 2019, Firenze
Stateful function object
struct IncrementBy
{
int delta;
void operator()(int& i) { i += delta; }
};
void func(std::vector<int>& v)
{
std::for_each(v.begin(), v.end(), IncrementBy{42});
}
Commit University – 9 Aprile 2019, Firenze
Stateful function object
struct IncrementBy
{
int delta;
void operator()(int& i) { i += delta; }
};
void func(std::vector<int>& v, int delta)
{
std::for_each(v.begin(), v.end(), IncrementBy{delta});
}
Commit University – 9 Aprile 2019, Firenze
I function object sono scomodi
• Scrivere function object è scomodo:
• Bisogna scrivere linee di codice inutili
• Bisogna inventarsi un nome che abbia senso
• Il codice del function object è «fisicamente» lontano dal punto in
cui l’oggetto viene effettivamente utilizzato
• Il fastidio è maggiore se il function object viene utilizzato una sola
volta, in un punto specifico del programma
Commit University – 9 Aprile 2019, Firenze
Lambda expression
void func(std::vector<int>& v, int delta)
{
std::for_each(v.begin(), v.end(), [delta](int& i) { i += delta; });
}
Commit University – 9 Aprile 2019, Firenze
Lambda expression
• Una lambda expression è un modo conciso e pratico per scrivere un
function object
• Per ogni lambda expression il compilatore:
• Crea un tipo chiusura o closure type, ovvero una classe con un
nome unico generato automaticamente che implementa il
function object descritto nell’espressione
• Quindi, sostituisce all’espressione un prvalue ottenuto mediante
costruendo un oggetto del closure type
Commit University – 9 Aprile 2019, Firenze
Closure type
struct lambda_10f32efa9bc
{
int delta;
void operator()(int& i) const { i += delta; }
};
void f(std::vector<int>& v, int delta)
{
std::for_each(v.begin(), v.end(), lambda_10f32efa9bc {delta});
}
Commit University – 9 Aprile 2019, Firenze
{ }corpo( )parametri specif
Sintassi di una lambda expression
[ ]catture
Questa parte è opzionale!
Commit University – 9 Aprile 2019, Firenze
{ }corpo
( )parametri specif
Sintassi di una lambda expression
[ ]catture
class lambda_10f32efa9bc
{
catture
public:
auto operator()(param) specif const
{
corpo
}
};
Commit University – 9 Aprile 2019, Firenze
Catture (Captures)
• Le catture servono per «catturare» valori e variabili locali dal
contesto in cui si trova la lambda expression
• Possiamo anche non catturare nulla con [], in quel caso il closure
type è stateless
• Possiamo catturare per valore o per riferimento
Commit University – 9 Aprile 2019, Firenze
Esempi di catture
// nessuna cattura
Button b;
b.OnClick([] { exit(); });
Commit University – 9 Aprile 2019, Firenze
Esempi di catture
// cattura per valore
Button b;
Color col;
b.OnClick([col] { SetWindowColor(col); });
Commit University – 9 Aprile 2019, Firenze
Esempi di catture
// cattura per riferimento
Button b;
Checkbox c;
b.OnClick([&c] { c.Toggle(); });
Commit University – 9 Aprile 2019, Firenze
Esempi di catture
// cattura per riferimento e per valore
Button b;
Checkbox c;
Color col;
b.OnClick([&c, col] { c.SetColor(col); });
Commit University – 9 Aprile 2019, Firenze
Capture-default “=”
Button b;
Checkbox c;
Color col;
// con =, tutte le variabili vengono catturate per valore
b.OnClick([=] { SetWindowColor(col); });
// ... tranne quelle indicate esplicitamente
b.OnClick([=, &c] { c.SetColor(col); });
Commit University – 9 Aprile 2019, Firenze
Capture-default “&”
Button b;
Checkbox c;
Color col;
// con &, tutte le variabili vengono catturate per riferimento
b.OnClick([&] { c.Toggle(); });
// ... tranne quelle indicate esplicitamente
b.OnClick([&, col] { c.SetColor(col); });
Commit University – 9 Aprile 2019, Firenze
Catturare “this”
• Se siamo in una funzione membro di una classe, è possibile anche
catturare il parametro nascosto this, questo permette di utilizzare
nel corpo della lambda i membri della classe (funzioni e campi non
statici)
Commit University – 9 Aprile 2019, Firenze
Catturare “this”
class Checbox {
void Toggle();
std::string label;
// ...
};
void Checkbox::Create(Button& b)
{
b.OnClick([this] { Toggle(); }); // this->Toggle()
b.OnDoubleClick([this] { Print(label); }); // this->label
}
Commit University – 9 Aprile 2019, Firenze
Catturare “this”
• Se è specificato il capture-default [&] allora this viene
automaticamente aggiunto alla lista delle catture se è necessario
• Attenzione: [this] cattura l’oggetto implicito (e tutti i suoi membri)
per riferimento!
• Da C++17 in poi, è possibile anche catturare l’oggetto implicito per
valore, utilizzando [*this]
Commit University – 9 Aprile 2019, Firenze
Catturare per riferimento
void f()
{
Checkbox c;
b.OnClick([&] { c.Toggle(); });
// ops! c viene distrutto qui!
}
• Attenzione quando catturate per riferimento, perché è vostra responsabilità
garantire che l’oggetto sia ancora valido quando la lambda sarà invocata!
Commit University – 9 Aprile 2019, Firenze
Cosa possiamo catturare?
• Oltre a this, solo le variabili locali, non statiche, non thread_local
possono essere catturate
• Variabili globali, statiche e thread_local possono essere usate senza
bisogno di catturarle, facendo però attenzione che la variabile sarà
usata direttamente, in quanto non fa parte della chiusura
• Variabili locali constexpr possono essere usate senza bisogno di
catturarle, in quanto il loro valore è noto a compile-time e può
essere sostituito inline
Commit University – 9 Aprile 2019, Firenze
Cattura e move-semantic
• Quando una variabile è catturata per copia, viene creato un membro
dato nella chiusura per ospitarne il valore, quindi l’espressione
lambda inizializza tale membro copiando la variabile
• In C++11 non è possibile catturare oggetti utilizzando il costruttore
di move, quindi non si possono catturare oggetti move-only
• Vedremo come questo è stato reso possibile a partire da C++14
Commit University – 9 Aprile 2019, Firenze
Espressioni Lambda in C++14
• Oltre alle variabili è possibile catturare anche il valore di una
espressione:
[x = expr] { /* x cattura il valore di expr */ }
• Questo consente la cattura per move anziché per copy:
[x = std::move(x)] { /* x catturato con move-ctor */ }
• Fate attenzione che la prima x dà il nome alla cattura, mentre la
seconda x si riferisce ad una variabile nel contesto della funzione
lambda
Commit University – 9 Aprile 2019, Firenze
Tipo del valore di ritorno
[] (int& i) { i += 1; }
Il valore di ritorno dell’operatore () è void
[] (int& i) { return i + 1; }
Il valore di ritorno dell’operatore () è int
• Grazie ad auto nella definizione dell’operatore (), non è necessario specificare
il valore di ritorno, che viene dedotto dal corpo della lambda expression
Commit University – 9 Aprile 2019, Firenze
Tipo del valore di ritorno
• Nei casi in cui è utile o necessario specificare un tipo esplicitamente, si può
usare una normale clausola trailing-return-type
auto operator()() -> float const
{
return 1;
}
[]() -> float { return 1; }
Commit University – 9 Aprile 2019, Firenze
struct lambda_10f32efa9bc
{
catture
auto operator()(param) specif const
{
corpo
}
};
const o non const?
• A causa del const aggiunto automaticamente, modificare i campi della
chiusura causa un errore di compilazione
Commit University – 9 Aprile 2019, Firenze
const per default
int f(std::vector<int> v)
{
int count = 0;
std::for_each(v.begin(), v.end(),
[=](int i){ if (i % 2 == 0) ++count; });
return count;
}
Commit University – 9 Aprile 2019, Firenze
A qualcuno piace mutable
void f(std::vector<int> v)
{
int index = 0;
std::for_each(v.begin(), v.end(),
[=](int i) mutable
{
++index;
if (i % 2 == 0) std::cout << index;
});
}
La keyword mutable
sopprime const
Commit University – 9 Aprile 2019, Firenze
Polymorphic lambda
[](auto x) { /* ... */ }
struct lambda_10f32efa9bc
{
template <class T>
auto operator()(T x) const
{
/* ... */
}
};
• È possibile dichiarare i parametri dell’espressione lambda con la keyword, di
fatto l’operatore () diventa un template!
Commit University – 9 Aprile 2019, Firenze
Memorizzare oggetti funzione
• Dato che ogni espressione lambda ha un suo tipo unico, il cui nome è
generato automaticamente, se volete memorizzare una lambda in
una variabile, potete usare auto:
auto x = [](){};
• Per passare una lambda come parametro di una funzione, potete
sempre usare un parametro template:
template <class Fun> void f(Fun f);
Commit University – 9 Aprile 2019, Firenze
std::function
std::function<void ()> f = [](){};
std::function<void ()> r = rand;
std::function<void (int&)> i = Increment{};
std::function<void (int&)> j = IncrementBy{10};
std::function<int (float)> g = [](float f) { return int(floor(f)); };
std::function<int (int)> h = [mult](int x) { return mult * x; };
Commit University – 9 Aprile 2019, Firenze
• Un’alternativa è usare il template di libreria std::function
• Grazie ad una tecnica chiamata type erasure un variabile di tipo std::function può
contenere qualsiasi oggetto funzione con una determinata firma
Attributi
Commit University – 9 Aprile 2019, Firenze
Attributi
• Spesso i compilatori hanno l’esigenza di aggiungere delle estensioni
al linguaggio (ad e.s.: __declspec, __attribute__, ecc.)
• Per ovviare al proliferare di sintassi proprietarie e non-compatibili,
sono stati introdotti gli attributi
• La grammatica consente di piazzare attributi in moltissimi posti, per
poterli “attaccare” a variabili, tipi, funzioni, semplici istruzioni, ecc.
Commit University – 9 Aprile 2019, Firenze
Attributi
• Le sintassi di un attributo sono:
• [[attributo]]
• [[attributo(parametri)]]
• [[namespace::attributo]]
• [[namespace::attributo(parametri)]]
• Gli attributi con namespace sono riservati per le estensioni
proprietarie dei compilatori
Commit University – 9 Aprile 2019, Firenze
Attributi
• Gli attributi possono, ad esempio, essere utilizzati per fornire
informazioni aggiuntive al compilatore al fine di:
• Generare o sopprimere messaggi diagnostici
• Ottimizzare il codice
• Ottenere effetti dipendenti dal compilatore non previsti dallo
standard
• Al compilatore è consentito di ignorare totalmente un attributo
Commit University – 9 Aprile 2019, Firenze
deprecated
[[deprecated(“usare l’overload col parametro param”)]]
void fun();
void fun(Type param);
Commit University – 9 Aprile 2019, Firenze
• L’attributo deprecated indica che una entità (variabile, tipo, funzione,
enumeratore, ecc.) non dovrebbe essere usato perché obsoleto
fallthrough
switch(expr)
{
case 0:
printf(“0”);
[[fallthrough]];
case 1:
printf(“1”);
break;
}
Commit University – 9 Aprile 2019, Firenze
• L’attributo fallthrough serve per dire al compilatore (e anche ai vostri
colleghi umani!) che avete deliberatamente omesso un break in uno switch
nodiscard
struct [[nodiscard]] error_info { /* ... */ };
error_info enable_missile_safety_mode();
void launch_missiles();
void test_missiles()
{
enable_missile_safety_mode(); // warning!
launch_missiles();
}
Commit University – 9 Aprile 2019, Firenze
• L’attributo nodiscard serve per indicare che il valore di ritorno di una funzione
non deve essere ignorato
<chrono>
Commit University – 9 Aprile 2019, Firenze
<chrono>
• <chrono> è la libreria per il calcolo di quantità temporali
• Sfrutta la meta-programmazione per garantire la massima
precisione, assenza di errori di arrotondamento e evitare gli errori di
conversione
• Si appoggia alla libreria <ratio> che fornisce dei tipi per
rappresentare le frazioni intere
Commit University – 9 Aprile 2019, Firenze
std::duration<Rep, Period>
• Il template duration serve per rappresentare una durata di tempo
• Rep è un tipo numerico (intero o floating point)
• Period è un’istanza di ratio<>
• Il valore viene memorizzato in una variabile di tipo Rep e l’unità di
misura è data dal Period misurato in secondi
Commit University – 9 Aprile 2019, Firenze
Tipi definiti per comodità d’uso
using nanoseconds = duration<int64_t, nano>;
using microseconds = duration<int64_t, micro>;
using milliseconds = duration<int64_t, milli>;
using seconds = duration<int64_t>;
using minutes = duration<int32_t, ratio<60>>;
using hours = duration<int32_t, ratio<3600>>;
Commit University – 9 Aprile 2019, Firenze
Conversione automatica
hours h { 2 }; // 2 ore
minutes m { 20 }; // 20 minuti
minutes t1 = h + m; // 140 minuti
hours t2 = h + m; // errore! perdita di precisione
Commit University – 9 Aprile 2019, Firenze
• Le normali operazioni tra oggetti duration effettuano automaticamente la
conversione quando possibile
std::time_point<Clock, Duration>
• Il template time_point rappresenta invece un punto specifico nella
linea temporale di uno specifico orologio
• Clock è una classe che soddisfa ai requisiti di «orologio»
• Duration è una instanza di duration<> che viene usato
internamente per rappresentare il tempo trascorso tra il punto
rappresentato e l’origine temporale dell’orologio (epoch)
Commit University – 9 Aprile 2019, Firenze
Orologi
• Lo standard definisce tre orologi:
• system_clock: l’orologio di sistema (quello della libreria C)
• steady_clock: un orologio che va sempre e solo in avanti in
maniera costante
• high_resolution_clock: l’orologio che ha la maggior risoluzione
disponibile
• Ogni orologio ha un metodo now() che ritorna il time_point attuale
• È possibile scrivere facilmente un orologio custom
Commit University – 9 Aprile 2019, Firenze
Operazioni tra tipi di <chrono>
duration + duration duration
duration - duration duration
time_point + duration time_point
time_point - duration time_point
time_point + time_point Errore!
time_point - time_point duration se i due time point sono
dello stesso orologio, altrimenti
errore!
Commit University – 9 Aprile 2019, Firenze
Uso di <chrono>
// Platform.h
void Wait(int milliseconds);
// Main.cpp
Wait(1000); // quanto tempo?
Commit University – 9 Aprile 2019, Firenze
// Platform.h
void Wait(milliseconds delay);
// Main.cpp
Wait(1000ms); // Ahh! Sono 1000
// millisecondi!
Wait(1s); // Stessa cosa, 1s
// viene convertito
// automaticamente
• I tipi chrono possono essere usati per migliorare le interfacce che hanno a che
fare col tempo
Il futuro è C++20
Commit University – 9 Aprile 2019, Firenze
C++20
• La C++ Committee sta lavorando sodo alla bozza del C++20 che è
già in feature freeze, le principali novità sono già consolidate
• Da qui alla pubblicazione, prevista per il 2020, si tratta unicamente
di piccole aggiunte o aggiustamenti
• Il C++20 promette un nuovo salto di qualità, con l’introduzione di
cambiamenti di ampio respiro che avranno un impatto significativo
sull’intero ecosistema di C++ e sullo stile di programmazione
Commit University – 9 Aprile 2019, Firenze
// C++03/11/14/17
#include <iostream>
int main()
{
std::cout << "hello, worldn";
}
// C++20
import std.io;
int main()
{
std::cout << "hello, worldn";
}
Moduli
L’introduzione dei moduli elimina
finalmente la fastidiosa dipendenza
dal preprocessore C per la gestione
degli #include
I moduli consentono
l’implementazione di toolchain più
ottimizzate e promuovono una
migliore separazione dei componenti
nei progetti di grandi dimensioni
Commit University – 9 Aprile 2019, Firenze
task<> ReadData()
{
auto conn = co_await Connect();
auto data = co_await conn->Read();
auto response = MakeResponse(data);
co_await conn->Write(response);
}
generator<int> fibonacci(int n)
{
int i0 = 1, i1 = 1;
for (;;)
{
co_yield i0;
int in = i0 + i1;
i0 = i1;
i1 = in;
}
}
Coroutine
Le coroutine consentono di scrivere
funzioni che possono essere sospese
e riprese dal punto di interruzione
Consentono di semplificare
notevolmente il codice asincrono e di
implementare i generatori lazy
C++20 non vincola ad uno specifico
runtime, ma fornisce un supporto
«aperto» alle personalizzazioni
Commit University – 9 Aprile 2019, Firenze
float sqrt(float x) [[pre: x >= 0]];
std::unique_ptr<Type> makeObj()
[[post ret: ret.get() != nullptr]];
void question(int x)
{
[[assert: x != 42]];
}
Contratti
I contratti introducono il supporto per
il cosiddetto design by contract
Consentono di scrivere pre-
condizioni, post-condizioni e
asserzioni in maniera integrata nel
linguaggio, senza l’utilizzo di macro
Commit University – 9 Aprile 2019, Firenze
// C++03/11/14/17
template <class RanIt>
void sort(RanIt begin, RanIt end);
// C++20
template <RandomIterator It>
void sort(It begin, It end);
template <RandomAccessRange Range>
void sort(Range& range);
Concetti
I concetti migliorano ulteriormente il
sistema dei template, introducendo la
possibilità di esprimere vincoli sui
parametri template, verificati prima di
istanziare l’intero template
Questo migliorerà i messaggi
diagnostici del codice che usa i
template e consentirà nuove tecniche
di meta-programmazione, meno
onerose per il compilatore
Commit University – 9 Aprile 2019, Firenze
// C++03/11/14/17
template <class T>
void func(T x) {}
// C++20
void func(auto x) {}
Template
I template ottengono una nuova
sintassi abbreviata, derivata da quelle
delle polimorphic lambda di C++14
Sarà quindi possibile scrivere una
funzione template… senza usare la
parola chiave template!
Commit University – 9 Aprile 2019, Firenze
std::vector<int> read_data();
std::vector<int> vi
= read_data()
| action::sort
| action::unique;
<range>
La libreria <range> un approccio
«moderno» agli algoritmi che sposta
l’attenzione dagli iteratori ai range:
coppie di iteratori che rappresentano
una sequenza iterabile di oggetti
La caratteristica peculiare è la
componibilità e riutilizzo del codice
Commit University – 9 Aprile 2019, Firenze
Range
• <range> è basato sulla libreria open source Ranges v3, a sua volta
evoluzione di Boost.Ranges
• Per chi volesse approfondire, la libreria Ranges v3, che è compatibile
con C++11/14/17, è scaricabile qui:
https://github.com/ericniebler/range-v3
ed è inclusa nell’ultima distribuzione di Visual Studio
• La versione C++20 utilizza i concetti, ma le differenze finiscono lì
Commit University – 9 Aprile 2019, Firenze
Grazie dell’attenzione
Domande?
Alberto Barbati
alberto@gamecentric.com
Twitter @gamecentric

More Related Content

Similar to Introduzione al C++ Moderno

Xamarin: Shared Library and Portable Class Library - Aristide Di Francesco - ...
Xamarin: Shared Library and Portable Class Library - Aristide Di Francesco - ...Xamarin: Shared Library and Portable Class Library - Aristide Di Francesco - ...
Xamarin: Shared Library and Portable Class Library - Aristide Di Francesco - ...Codemotion
 
Corso di Basi e Fondamenti di Programmazione in C++ Lezione 1
Corso di Basi e Fondamenti di Programmazione in C++ Lezione 1Corso di Basi e Fondamenti di Programmazione in C++ Lezione 1
Corso di Basi e Fondamenti di Programmazione in C++ Lezione 1Daniele Falamesca
 
Code Generation con i templates T4 in visual studio
Code Generation con i templates T4 in visual studioCode Generation con i templates T4 in visual studio
Code Generation con i templates T4 in visual studioMarco Parenzan
 
DotNetToscana - Sessione TypeScript
DotNetToscana - Sessione TypeScriptDotNetToscana - Sessione TypeScript
DotNetToscana - Sessione TypeScriptSinergia Totale
 
Deep diving C# 4 (Raffaele Rialdi)
Deep diving C# 4 (Raffaele Rialdi)Deep diving C# 4 (Raffaele Rialdi)
Deep diving C# 4 (Raffaele Rialdi)DotNetMarche
 
Lezione 4 (7 marzo 2012)
Lezione 4 (7 marzo 2012)Lezione 4 (7 marzo 2012)
Lezione 4 (7 marzo 2012)STELITANO
 
Creare Documenti Scientifici Accessibili
Creare Documenti Scientifici AccessibiliCreare Documenti Scientifici Accessibili
Creare Documenti Scientifici AccessibiliValeria Brigatti
 
Corso di Formazione: Redattore di Sito Web Dipartimentale
Corso di Formazione: Redattore di Sito Web DipartimentaleCorso di Formazione: Redattore di Sito Web Dipartimentale
Corso di Formazione: Redattore di Sito Web DipartimentaleUniversity of Padua
 
Python@Unina - Theory
Python@Unina - TheoryPython@Unina - Theory
Python@Unina - TheoryNaLUG
 
Da JavaScript a TypeScript
Da JavaScript a TypeScriptDa JavaScript a TypeScript
Da JavaScript a TypeScriptRoberto Messora
 
Evoluzione degli strumenti di sviluppo Microsoft
Evoluzione degli strumenti di sviluppo MicrosoftEvoluzione degli strumenti di sviluppo Microsoft
Evoluzione degli strumenti di sviluppo MicrosoftMassimo Bonanni
 
Clean programming 2020-01-25 @ Modena Tech Summit
Clean programming 2020-01-25 @ Modena Tech SummitClean programming 2020-01-25 @ Modena Tech Summit
Clean programming 2020-01-25 @ Modena Tech SummitDavide Muzzarelli
 
2010.11.19 iniziare con F#
2010.11.19 iniziare con F#2010.11.19 iniziare con F#
2010.11.19 iniziare con F#Marco Parenzan
 
2010.11.19 iniziare con f#
2010.11.19 iniziare con f#2010.11.19 iniziare con f#
2010.11.19 iniziare con f#Marco Parenzan
 
1 - Introduzione al corso 19/20
1 - Introduzione al corso 19/201 - Introduzione al corso 19/20
1 - Introduzione al corso 19/20Giuseppe Vizzari
 
Slide typescript - xe dotnet - Codemotion Rome 2015
Slide typescript - xe dotnet - Codemotion Rome 2015Slide typescript - xe dotnet - Codemotion Rome 2015
Slide typescript - xe dotnet - Codemotion Rome 2015Codemotion
 

Similar to Introduzione al C++ Moderno (20)

DDive11 - Notes Moon Attack
DDive11 - Notes Moon AttackDDive11 - Notes Moon Attack
DDive11 - Notes Moon Attack
 
Xamarin: Shared Library and Portable Class Library - Aristide Di Francesco - ...
Xamarin: Shared Library and Portable Class Library - Aristide Di Francesco - ...Xamarin: Shared Library and Portable Class Library - Aristide Di Francesco - ...
Xamarin: Shared Library and Portable Class Library - Aristide Di Francesco - ...
 
Corso di Basi e Fondamenti di Programmazione in C++ Lezione 1
Corso di Basi e Fondamenti di Programmazione in C++ Lezione 1Corso di Basi e Fondamenti di Programmazione in C++ Lezione 1
Corso di Basi e Fondamenti di Programmazione in C++ Lezione 1
 
Code Generation con i templates T4 in visual studio
Code Generation con i templates T4 in visual studioCode Generation con i templates T4 in visual studio
Code Generation con i templates T4 in visual studio
 
DotNetToscana - Sessione TypeScript
DotNetToscana - Sessione TypeScriptDotNetToscana - Sessione TypeScript
DotNetToscana - Sessione TypeScript
 
Guida C++
Guida C++Guida C++
Guida C++
 
Deep diving C# 4 (Raffaele Rialdi)
Deep diving C# 4 (Raffaele Rialdi)Deep diving C# 4 (Raffaele Rialdi)
Deep diving C# 4 (Raffaele Rialdi)
 
RomaJS June 2022
RomaJS June 2022RomaJS June 2022
RomaJS June 2022
 
Lezione 4 (7 marzo 2012)
Lezione 4 (7 marzo 2012)Lezione 4 (7 marzo 2012)
Lezione 4 (7 marzo 2012)
 
Creare Documenti Scientifici Accessibili
Creare Documenti Scientifici AccessibiliCreare Documenti Scientifici Accessibili
Creare Documenti Scientifici Accessibili
 
Corso di Formazione: Redattore di Sito Web Dipartimentale
Corso di Formazione: Redattore di Sito Web DipartimentaleCorso di Formazione: Redattore di Sito Web Dipartimentale
Corso di Formazione: Redattore di Sito Web Dipartimentale
 
Python@Unina - Theory
Python@Unina - TheoryPython@Unina - Theory
Python@Unina - Theory
 
Da JavaScript a TypeScript
Da JavaScript a TypeScriptDa JavaScript a TypeScript
Da JavaScript a TypeScript
 
Intervento osta ccms marathon 2017
Intervento osta ccms marathon 2017Intervento osta ccms marathon 2017
Intervento osta ccms marathon 2017
 
Evoluzione degli strumenti di sviluppo Microsoft
Evoluzione degli strumenti di sviluppo MicrosoftEvoluzione degli strumenti di sviluppo Microsoft
Evoluzione degli strumenti di sviluppo Microsoft
 
Clean programming 2020-01-25 @ Modena Tech Summit
Clean programming 2020-01-25 @ Modena Tech SummitClean programming 2020-01-25 @ Modena Tech Summit
Clean programming 2020-01-25 @ Modena Tech Summit
 
2010.11.19 iniziare con F#
2010.11.19 iniziare con F#2010.11.19 iniziare con F#
2010.11.19 iniziare con F#
 
2010.11.19 iniziare con f#
2010.11.19 iniziare con f#2010.11.19 iniziare con f#
2010.11.19 iniziare con f#
 
1 - Introduzione al corso 19/20
1 - Introduzione al corso 19/201 - Introduzione al corso 19/20
1 - Introduzione al corso 19/20
 
Slide typescript - xe dotnet - Codemotion Rome 2015
Slide typescript - xe dotnet - Codemotion Rome 2015Slide typescript - xe dotnet - Codemotion Rome 2015
Slide typescript - xe dotnet - Codemotion Rome 2015
 

More from Commit University

Nell’iperspazio con Rocket: il Framework Web di Rust!
Nell’iperspazio con Rocket: il Framework Web di Rust!Nell’iperspazio con Rocket: il Framework Web di Rust!
Nell’iperspazio con Rocket: il Framework Web di Rust!Commit University
 
Crea il tuo assistente AI con lo Stregatto (open source python framework)
Crea il tuo assistente AI con lo Stregatto (open source python framework)Crea il tuo assistente AI con lo Stregatto (open source python framework)
Crea il tuo assistente AI con lo Stregatto (open source python framework)Commit University
 
Breaking REST Chains_ A Fastify & Mercurius Pathway to GraphQL Glory.pdf
Breaking REST Chains_ A Fastify & Mercurius Pathway to GraphQL Glory.pdfBreaking REST Chains_ A Fastify & Mercurius Pathway to GraphQL Glory.pdf
Breaking REST Chains_ A Fastify & Mercurius Pathway to GraphQL Glory.pdfCommit University
 
Accelerating API Development: A Pit Stop with Gin-Gonic in Golang-Slide.pdf
Accelerating API Development: A Pit Stop with Gin-Gonic in Golang-Slide.pdfAccelerating API Development: A Pit Stop with Gin-Gonic in Golang-Slide.pdf
Accelerating API Development: A Pit Stop with Gin-Gonic in Golang-Slide.pdfCommit University
 
Collaborazione, Decisionalità e Gestione della Complessità nel Tempo: cosa ...
Collaborazione, Decisionalità e Gestione della Complessità nel Tempo: cosa ...Collaborazione, Decisionalità e Gestione della Complessità nel Tempo: cosa ...
Collaborazione, Decisionalità e Gestione della Complessità nel Tempo: cosa ...Commit University
 
Commit - Qwik il framework che ti stupirà.pptx
Commit - Qwik il framework che ti stupirà.pptxCommit - Qwik il framework che ti stupirà.pptx
Commit - Qwik il framework che ti stupirà.pptxCommit University
 
Sviluppare da zero una Angular Web App per la PA
Sviluppare da zero una Angular Web App per la PASviluppare da zero una Angular Web App per la PA
Sviluppare da zero una Angular Web App per la PACommit University
 
Backstage l'Internal Developer Portal Open Source per una migliore Developer ...
Backstage l'Internal Developer Portal Open Source per una migliore Developer ...Backstage l'Internal Developer Portal Open Source per una migliore Developer ...
Backstage l'Internal Developer Portal Open Source per una migliore Developer ...Commit University
 
Prisma the ORM that node was waiting for
Prisma the ORM that node was waiting forPrisma the ORM that node was waiting for
Prisma the ORM that node was waiting forCommit University
 
Decision-making for Software Development Teams - Commit University
Decision-making for Software Development Teams - Commit UniversityDecision-making for Software Development Teams - Commit University
Decision-making for Software Development Teams - Commit UniversityCommit University
 
Component Design Pattern nei Game Engine.pdf
Component Design Pattern nei Game Engine.pdfComponent Design Pattern nei Game Engine.pdf
Component Design Pattern nei Game Engine.pdfCommit University
 
Un viaggio alla scoperta dei Language Models e dell’intelligenza artificiale ...
Un viaggio alla scoperta dei Language Models e dell’intelligenza artificiale ...Un viaggio alla scoperta dei Language Models e dell’intelligenza artificiale ...
Un viaggio alla scoperta dei Language Models e dell’intelligenza artificiale ...Commit University
 
Prototipazione Low-Code con AWS Step Functions
Prototipazione Low-Code con AWS Step FunctionsPrototipazione Low-Code con AWS Step Functions
Prototipazione Low-Code con AWS Step FunctionsCommit University
 
KMM survival guide: how to tackle struggles between Kotlin and Swift
KMM survival guide: how to tackle struggles between Kotlin and SwiftKMM survival guide: how to tackle struggles between Kotlin and Swift
KMM survival guide: how to tackle struggles between Kotlin and SwiftCommit University
 
Da Vuex a Pinia: come fare la migrazione
Da Vuex a Pinia: come fare la migrazioneDa Vuex a Pinia: come fare la migrazione
Da Vuex a Pinia: come fare la migrazioneCommit University
 
Orchestrare Micro-frontend con micro-lc
Orchestrare Micro-frontend con micro-lcOrchestrare Micro-frontend con micro-lc
Orchestrare Micro-frontend con micro-lcCommit University
 
Fastify has defeated Lagacy-Code
Fastify has defeated Lagacy-CodeFastify has defeated Lagacy-Code
Fastify has defeated Lagacy-CodeCommit University
 

More from Commit University (20)

Nell’iperspazio con Rocket: il Framework Web di Rust!
Nell’iperspazio con Rocket: il Framework Web di Rust!Nell’iperspazio con Rocket: il Framework Web di Rust!
Nell’iperspazio con Rocket: il Framework Web di Rust!
 
Crea il tuo assistente AI con lo Stregatto (open source python framework)
Crea il tuo assistente AI con lo Stregatto (open source python framework)Crea il tuo assistente AI con lo Stregatto (open source python framework)
Crea il tuo assistente AI con lo Stregatto (open source python framework)
 
Breaking REST Chains_ A Fastify & Mercurius Pathway to GraphQL Glory.pdf
Breaking REST Chains_ A Fastify & Mercurius Pathway to GraphQL Glory.pdfBreaking REST Chains_ A Fastify & Mercurius Pathway to GraphQL Glory.pdf
Breaking REST Chains_ A Fastify & Mercurius Pathway to GraphQL Glory.pdf
 
Accelerating API Development: A Pit Stop with Gin-Gonic in Golang-Slide.pdf
Accelerating API Development: A Pit Stop with Gin-Gonic in Golang-Slide.pdfAccelerating API Development: A Pit Stop with Gin-Gonic in Golang-Slide.pdf
Accelerating API Development: A Pit Stop with Gin-Gonic in Golang-Slide.pdf
 
Slide-10years.pdf
Slide-10years.pdfSlide-10years.pdf
Slide-10years.pdf
 
Collaborazione, Decisionalità e Gestione della Complessità nel Tempo: cosa ...
Collaborazione, Decisionalità e Gestione della Complessità nel Tempo: cosa ...Collaborazione, Decisionalità e Gestione della Complessità nel Tempo: cosa ...
Collaborazione, Decisionalità e Gestione della Complessità nel Tempo: cosa ...
 
Vue.js slots.pdf
Vue.js slots.pdfVue.js slots.pdf
Vue.js slots.pdf
 
Commit - Qwik il framework che ti stupirà.pptx
Commit - Qwik il framework che ti stupirà.pptxCommit - Qwik il framework che ti stupirà.pptx
Commit - Qwik il framework che ti stupirà.pptx
 
Sviluppare da zero una Angular Web App per la PA
Sviluppare da zero una Angular Web App per la PASviluppare da zero una Angular Web App per la PA
Sviluppare da zero una Angular Web App per la PA
 
Backstage l'Internal Developer Portal Open Source per una migliore Developer ...
Backstage l'Internal Developer Portal Open Source per una migliore Developer ...Backstage l'Internal Developer Portal Open Source per una migliore Developer ...
Backstage l'Internal Developer Portal Open Source per una migliore Developer ...
 
Prisma the ORM that node was waiting for
Prisma the ORM that node was waiting forPrisma the ORM that node was waiting for
Prisma the ORM that node was waiting for
 
Decision-making for Software Development Teams - Commit University
Decision-making for Software Development Teams - Commit UniversityDecision-making for Software Development Teams - Commit University
Decision-making for Software Development Teams - Commit University
 
Component Design Pattern nei Game Engine.pdf
Component Design Pattern nei Game Engine.pdfComponent Design Pattern nei Game Engine.pdf
Component Design Pattern nei Game Engine.pdf
 
Un viaggio alla scoperta dei Language Models e dell’intelligenza artificiale ...
Un viaggio alla scoperta dei Language Models e dell’intelligenza artificiale ...Un viaggio alla scoperta dei Language Models e dell’intelligenza artificiale ...
Un viaggio alla scoperta dei Language Models e dell’intelligenza artificiale ...
 
Prototipazione Low-Code con AWS Step Functions
Prototipazione Low-Code con AWS Step FunctionsPrototipazione Low-Code con AWS Step Functions
Prototipazione Low-Code con AWS Step Functions
 
KMM survival guide: how to tackle struggles between Kotlin and Swift
KMM survival guide: how to tackle struggles between Kotlin and SwiftKMM survival guide: how to tackle struggles between Kotlin and Swift
KMM survival guide: how to tackle struggles between Kotlin and Swift
 
Da Vuex a Pinia: come fare la migrazione
Da Vuex a Pinia: come fare la migrazioneDa Vuex a Pinia: come fare la migrazione
Da Vuex a Pinia: come fare la migrazione
 
Orchestrare Micro-frontend con micro-lc
Orchestrare Micro-frontend con micro-lcOrchestrare Micro-frontend con micro-lc
Orchestrare Micro-frontend con micro-lc
 
Fastify has defeated Lagacy-Code
Fastify has defeated Lagacy-CodeFastify has defeated Lagacy-Code
Fastify has defeated Lagacy-Code
 
SwiftUI vs UIKit
SwiftUI vs UIKitSwiftUI vs UIKit
SwiftUI vs UIKit
 

Introduzione al C++ Moderno

  • 1. Alberto Barbati alberto@gamecentric.com Introduzione al C++ Moderno Commit University 9 Aprile 2019, Firenze
  • 2. Alberto Barbati Programmatore C++ entusiasta dal 1990 Nella game industry dal 2000 Sviluppatore e formatore Core Trainer di Game Programming presso Digital Bros Game Academy Segue i lavori della C++ Committee dal 2008 Commit University – 9 Aprile 2019, Firenze
  • 3. Cosa vedremo oggi? • Brevissima storia del C++ • Le principali novità introdotte nel ciclo C++11/14/17 • Di queste, vedremo, nel dettaglio: • Move semantic • Smart pointer • Lambda expression • Un’anteprima di quello che ci aspetta nel C++20 Commit University – 9 Aprile 2019, Firenze
  • 5. Inizi del C++ Se vogliamo scegliere una data di nascita al linguaggio C++, allora sicuramente dobbiamo andare indietro fino al 1985, quando Bjarne Stroustrup pubblica il libro «The C++ Programming Language» Commit University – 9 Aprile 2019, Firenze
  • 6. 1998: uno standard internazionale • Il linguaggio subisce varie revisioni, ma viene presto percepito il rischio della creazione di dialetti non compatibili • Per questo motivo, nel 1998 un gruppo di lavoro in seno all’organizzazione ISO, pubblica la prima versione «Standard» del linguaggio, chiamata a posteriori C++98 • La costituita C++ Committee pubblica quindi successivamente una seconda revisione dello Standard nel 2003, chiamata C++03 Commit University – 9 Aprile 2019, Firenze
  • 7. C++ «Classico» • Il C++03 propone: • Supporto per la programmazione a oggetti, tramite i concetti di classi, ereditarietà e polimorfismo • Supporto per la programmazione generica con template • Overloading degli operatori • Una libreria standard (la cosiddetta STL) basata sui template Commit University – 9 Aprile 2019, Firenze
  • 8. Il C++ si diffonde • Il C++03 inizia ad essere adottato e riscuote un moderato successo in alcuni ambiti, principalmente come alternativa al linguaggio C da cui trae ispirazione • È anche soggetto ad aspre critiche per la sua percepita complessità e i problemi delle sue implementazioni e in particolare della STL • Il lungo periodo fino al 2011 è cruciale, perché c’è aspra concorrenza da parte di altri linguaggi e la C++ Committee fatica a trovare un accordo su come far progredire il linguaggio Commit University – 9 Aprile 2019, Firenze
  • 9. 2011: C++ «Moderno» • Nel 2011 viene finalmente pubblicato un nuovo standard, il C++11 • Nel modifiche introdotte sono di tale portata da segnare una netta cesura rispetto al C++03 e iniziare l’era del C++ Moderno • La commissione inoltre si impegna a fornire nuove revisioni del linguaggio con cadenza triennale, per mantenere alta l’attenzione e evitare di alienarsi la comunità dei programmatori come stava per accadere nel periodo 2003-2011 • Da allora sono stati pubblicati C++14 e C++17 Commit University – 9 Aprile 2019, Firenze
  • 10. Dimensione dello Standard C++ in pagine Commit University – 9 Aprile 2019, Firenze C++03 900 C++11 1300 C++14 1400 C++17 1600 C++20 1800
  • 11. Commit University – 9 Aprile 2019, Firenze Novità del C++ moderno
  • 12. Cosa rende il C++11 «moderno»? • Le principali innovazioni del C++11 sono: • Un nuovo object model che introduce il concetto di move- semantic • Il potenziamento dei template che apre la porta alla meta- programmazione • Auto, constexpr, lambda expression, range-for e attributi • Nuove librerie che includono molti vocabulary type • Supporto per il multi-threading Commit University – 9 Aprile 2019, Firenze
  • 13. Stile di programmazione moderno • Le modifiche al linguaggio hanno un profondo effetto sullo stile di programmazione: • Gestione degli oggetti sull’heap tramite contenitori e smart pointer, drastica riduzione dell’uso di puntatori, new, delete, ecc. • Uso dei vocabulary types per rendere il codice più espressivo • Aumento del codice eseguito a compile-time (constexpr e meta- programmazione) • Spostamento dell’enfasi dal polimorfismo dinamico (funzioni virtuali) al polimorfismo statico (template) Commit University – 9 Aprile 2019, Firenze
  • 14. Risorse Utili C++ • isocpp.org: sito ufficiale della C++ Committee • cppreference.com: la «wikipedia» del C++11/14/17/20 • github.com/isocpp/CppCoreGuidelines: Core Guidelines del C++ • github.com/cplusplus/draft: sorgenti LaTeX dello Standard C++ • godbolt.org: compilatore online di C++ Commit University – 9 Aprile 2019, Firenze
  • 15. Novità del linguaggio Commit University – 9 Aprile 2019, Firenze
  • 16. auto i = 0; // i è un int auto f = 0.0; / f è un double // C++03 std::vector<int>::iterator it = std::find(vec.begin(), vec.end(), 0); // C++11 auto it = std::find(vec.begin(), vec.end(), 0); auto La parola chiave auto, utilizzata nelle prime versioni del C++ e deprecata in C++03 per inutilizzo, è stata riabilitata in C++11 con un nuovo significato Consente di dichiarare una variabile deducendo il tipo dall’espressione usata per inizializzarla Rende il codice molto più conciso, in particolare quando ci sono di mezzo i template Commit University – 9 Aprile 2019, Firenze
  • 17. auto func() // ritorna int { return 0; } // il valore di ritorno è il tipo // dell’espressione a * b template <class T1, class T2> auto mul(T1 a, T2 b) { return a * b; } auto La parola chiave auto può anche essere usata per dedurre il valore di ritorno di una funzione Questo può essere utile nel codice generico, quando il tipo non è noto a priori Commit University – 9 Aprile 2019, Firenze
  • 18. Linee guida: auto • Il consiglio dei guru è «almost always auto» ovvero utilizzare auto praticamente ovunque, quando possibile • C’è da evidenziare una certa riluttanza della community ad applicare questa linea guida, che è dovuta spesso a preconcetti sulla presunta minore leggibilità del codice e sulla perdita di controllo sui tipi • Dopo anni di programmazione C++11, la mia personale esperienza è che questi dubbi sono infondati Commit University – 9 Aprile 2019, Firenze
  • 19. // C++03/11 int array[3] = { 1, 2, 3 }; string s("hello, world"); // C++11 int array[3] { 1, 2, 3 }; string s {"hello, world"}; Inizializzazione «uniforme» Viene introdotta una nuova sintassi di inizializzazione per uniformare l’inizializzazione di oggetti di classe e aggregati In sostanza sostituisce le parentesi tonde () con le graffe {} Risolve alcuni problemi di parsing (most vexing parse) e permette di avere una sintassi per l’inizializzazione di default Commit University – 9 Aprile 2019, Firenze
  • 20. Most vexing parse e inizializzazione Commit University – 9 Aprile 2019, Firenze • Inizializza una stringa vuotastd::string s; • Inizializza una stringa vuotastd::string s(""); • Dichiara una funzione s che non accetta parametri!std::string s(); • Inizializza una stringa vuotastd::string s{}; • Inizializza una stringa vuotastd::string s{""};
  • 21. Most vexing parse e inizializzazione Commit University – 9 Aprile 2019, Firenze • La variabile non viene inizializzata!int i; • Dichiara una funzione i che non accetta parametri!int i(); • Inizializza i al valore 0int i{};
  • 22. Inizializzazione uniforme in altri contesti • L’inizializzazione uniforme si può usare in molti altri contesti • Conversione di tipo: Tipo(argomenti) ➔ Tipo{argomenti} • Questo risolve un annoso problema… non tutti sanno che: • Tipo(a, b) invoca un costruttore con due parametri, se c’è • Tipo(a) è equivalente a (Tipo)a che è un C-style cast! Potrebbe invocare il costruttore con un parametro, ma anche no • La sintassi Tipo{a} invoca sempre e solo il costruttore Commit University – 9 Aprile 2019, Firenze
  • 23. Inizializzazione uniforme in altri contesti • In molti casi, il tipo può essere dedotto dal contesto: • func({ argomenti }) deduce il tipo dal tipo del parametro • return { argomenti }; deduce il tipo dal tipo di ritorno • Costruttori: • MyClass(argomenti) : x { expr } { … } • L’inizializzazione uniforme è più sicura, perché evita il narrowing: • int y { x }; // ok solo se x è rappresentabile in int Commit University – 9 Aprile 2019, Firenze
  • 24. Inizializzazione di lista • Nuova feature e piccolo gotcha: la sintassi uniforme consente la cosiddetta list-initialization, che permette di passare al costruttore una lista di lunghezza variabile di oggetti • È utilizzata prevalentemente per i contenitori: • std::vector<int> vec { 1, 2, 3, 4, 5 }; • Attenzione però! • std::vector<int> vec { 5 }; // vec è { 5 } • std::vector<int> vec ( 5 ); // vec è { 0, 0, 0, 0, 0 } Commit University – 9 Aprile 2019, Firenze
  • 25. Linee guida: inizializzazione uniforme • La linea guida è: • Preferire la sintassi di inizializzazione uniforme {} alla sintassi «tradizionale» con () • L’unica eccezione è per i tipi che consentono la list-initialization per cui le due sintassi hanno effetti differenti • Nei tipi definiti dall’utente, evitare di introdurre ambiguità tra la list-initialization e gli altri costruttori Commit University – 9 Aprile 2019, Firenze
  • 26. // C++03 for (std::vector<int>::iterator it = vec.begin(); it != vec.end(); ++it) { /* ... */} // C++11 for (int i : vec) { /* ... */} for each Viene introdotta una nuova sintassi compatta per iterare tutti gli elementi di un contenitore La sintassi funziona con gli array, tutti i contenitori della libreria standard ed è estendibile anche per i tipi definiti dall’utente Commit University – 9 Aprile 2019, Firenze
  • 27. template <class... Args> class MyClass {}; MyClass<int, long> x; MyClass<float, double> y; template <class... Args> void fun(Args... Args); fun(0, 0l); fun("", 0, 3.14); Template variadici Diventa possibile avere un template con un numero variabile di parametri Questo vale sia per i template di classi che per quelli di funzione Commit University – 9 Aprile 2019, Firenze
  • 28. struct TypeWithLongName; // C++03 typedef TypeWithLongName Type; typedef void (*Func)(int, double); // C++11 using Type = TypeWithLongName; using Func = void (*)(int, double); template <class T> using MyVector = std::vector<T, MyAllocator<T>>; MyVector<int> v; using La parola using può essere usata per introdurre alias di tipi Si ottiene lo stesso effetto di typedef, ma la sintassi è molto più semplice e leggibile Inoltre, con using si può definire un template di alias Commit University – 9 Aprile 2019, Firenze
  • 29. struct Base { virtual void f(); virtual void g(int); virtual void h(); }; struct Derived : Base { void f() override; // ok void g() override; // errore! void h() final; }; struct MostDerived : Derived { void h(); // errore! }; final e override Vengono aggiunge due parole chiave contestuali, final e override per un maggior controllo del polimorfismo override richiede al compilatore di verificare che esista una funzione virtuale corrispondente final impedisce l’override di una funzione virtuale. Se usata sull’intera classe, impedisce la derivazione Commit University – 9 Aprile 2019, Firenze
  • 30. int f(); // i viene inizializzata a runtime static int i { f() }; // errore! f() non può essere // eseguita a compile time constexpr int j { f() }; // k è inizializzata a compile time constexpr int g() { /* ... */ } constexpr int k { g() }; constexpr Viene aggiunta una nuova parola chiave constexpr per: • Forzare l’inizializzazione di una variabile a compile time • Indicare funzioni che possono essere eseguite a compile time Commit University – 9 Aprile 2019, Firenze
  • 31. Novità della libreria standard Commit University – 9 Aprile 2019, Firenze
  • 32. // contiene un int, un float e // un long std::tuple<int, float, long> t; // contiene un int oppure un float // oppure un long (C++17) std::variant<int, float, long> v; // contiene qualsiasi tipo (C++17) std::any a; // contiene un int oppure no (C++17) std::optional<int> o; Tipi vocabolario La libreria introduce molti vocabulary type per rendere il codice più espressivo Commit University – 9 Aprile 2019, Firenze
  • 33. Nuovi contenitori • Vengono introdotti nuovi contenitori: • array: array di lunghezza fissa (pur ricalcando di fatto la funzionalità dei normali array C, offre alcuni vantaggi) • forward_list: lista linkata a link singolo • unordered_set, unordered_multiset, unordered_map e unordered_multimap: versioni hashed dei contenitori associativi Commit University – 9 Aprile 2019, Firenze
  • 34. Supporto per il multithread Commit University – 9 Aprile 2019, Firenze • La libreria offre le seguenti funzionalità • Una classe thread per lanciare e eseguire thread • Classi per gestire gli accessi atomici alla memoria • Classi per mutex, lock e condition variables • Classi per la gestione della concorrenza tramite future
  • 35. std::future int calcolo(); int main() { std::future<int> fut = std::async(calcolo); /* il codice non è bloccato, l’esecuzione può proseguire */ int i = fut.get(); // bloccante: attende il termine del // calcolo, se necessario /* ... */ } Commit University – 9 Aprile 2019, Firenze
  • 36. E tanto altro… • Smart pointer (che vedremo nella sezione dedicata) • Regular expression • error_code e error_condition per astrarre i codici di errore • (C++17) Allocatori polimorfici (memory resource) • (C++17) path per astrarre l’accesso al file system • (C++17) Algoritmi paralleli • Ecc. Commit University – 9 Aprile 2019, Firenze
  • 37. Commit University – 9 Aprile 2019, Firenze Move semantic
  • 38. Oggetto/Valore • Consideriamo la seguente variabile: int var = 42; • La variabile è memorizzata un una regione di «memoria», che sia sullo stack, heap o in un registro, è irrilevante • Il suo «valore» è unicamente determinato dalla pattern di bit contenuta nella regione • Copiare il valore della variabile, significa banalmente, copiare la pattern di bit in un’altra regione Commit University – 9 Aprile 2019, Firenze
  • 39. Tipi complessi • Poiché in C++ possiamo definire nuovi tipi, è facile pensare a tipi più complessi per cui la definizione di «valore» data nella slide precedente è limitante • Pensate, ad esempio ad un contenitore di oggetti o a una stringa • In questi casi, si potrebbe dire che il «valore» non sia determinato unicamente dai byte della regione in cui è memorizzato… Commit University – 9 Aprile 2019, Firenze
  • 40. Il caso di std::vector • L’oggetto di libreria std::vector è un contenitore in grado di gestire una sequenza arbitraria di altri oggetti, il cui numero è limitato unicamente dalla memoria disponibile e può variare durante l’esecuzione • Eppure la dimensione in memoria di un oggetto std::vector è fissa • L’implementazione tipica di std::vector consiste in: • Un puntatore ad una zona di memoria allocata sul heap per memorizzare la sequenza degli oggetti «contenuti» • Un contatore degli oggetti contenuti • E poco altro… Commit University – 9 Aprile 2019, Firenze
  • 41. Costruire un vettore • Considerate questo codice: std::vector<int> vec1 { 1, 2, 3, 4 }; • Questa riga costruisce un vettore di interi e lo inizializza con una sequenza di quattro interi • Viene chiamato uno dei costruttori di std::vector che, tra le altre cose, alloca sulla heap memoria sufficiente per i quattro interi Commit University – 9 Aprile 2019, Firenze
  • 42. Distruggere un vettore • Diciamo che vec1 ha la custodia della memoria allocata, in quanto ha la responsabilità di rilasciarla, quando non servirà più • Quando la variabile vec1 esce di scope, viene chiamato il distruttore di std::vector che rilascia la memoria e la custodia termina Commit University – 9 Aprile 2019, Firenze
  • 43. Copiare un vettore • Cosa succede se copio vec1? std::vector<int> vec2 = vec1; • Se il C++ si limitasse a copiare la pattern di bit di vec1 in vec2, come un normale intero, farebbe un pessimo servizio! • Infatti il risultato sarebbe quello di avere due variabili che fanno riferimento alla stessa zona di memoria sullo heap e quindi non potremmo più distinguere quale delle due ha la custodia della memoria! Commit University – 9 Aprile 2019, Firenze
  • 44. Costruttore di copia • In C++03, sappiamo che in questo caso viene invocato uno speciale costruttore di std::vector chiamato costruttore di copia che alloca memoria per altri 4 interi e copia la sequenza • Il risultato che è otteniamo due vettori che contengono copie degli stessi oggetti • Ciascun vettore ha la custodia della propria zona di memoria e non c’è ambiguità su chi deve rilasciare cosa Commit University – 9 Aprile 2019, Firenze
  • 45. Spreco std::vector<int> makeVector() { std::vector<int> vec; /* popola v con valori */ return vec; } Commit University – 9 Aprile 2019, Firenze • Il fatto che l’intera sequenza venga copiata è a volte motivo di spreco • Considerate la seguente funzione
  • 46. Workaround void makeVector(std::vector<int>& v) { /* popola v con valori */ } Commit University – 9 Aprile 2019, Firenze • L’istruzione return fa una copia il vettore contenuto nella variabile locale vec nel contesto del chiamante della funzione, quindi distrugge vec! • Questo è il motivo per cui molte linee guida di programmazione C++03 rifiutano di scrivere funzioni in quel modo, preferendo lo stile più efficiente, ma innaturale e più scomodo da usare:
  • 47. Il costruttore di move • Il C++11 introduce un nuovo tipo di costruttore, il costruttore di spostamento o move, che implementa la semantica di «spostare il valore» • Utilizzando un siffatto costruttore, il vettore locale vec nell’esempio può passare la custodia al contesto del chiamante ed evitare l’inutile doppia allocazione • La prima stesura della funzione makeVector, che è più naturale, improvvisamente diventa efficiente come l’altra! Commit University – 9 Aprile 2019, Firenze
  • 48. Copia vs. Move • La differenza fondamentale tra una copia e uno spostamento è che nel caso della copia l’oggetto «sorgente» rimane invariato, mentre nel caso dello spostamento l’oggetto che fornisce il valore da spostare deve essere modificato, tanto quanto l’oggetto che riceve il valore Commit University – 9 Aprile 2019, Firenze
  • 49. Operazione di copia Commit University – 9 Aprile 2019, Firenze ? Destinazione {1, 2, 3, 4} Sorgente
  • 50. Operazione di copia Commit University – 9 Aprile 2019, Firenze {1, 2, 3, 4} Destinazione {1, 2, 3, 4} Sorgente L’oggetto sorgente non viene modificato
  • 51. Operazione di spostamento Commit University – 9 Aprile 2019, Firenze ? Destinazione {1, 2, 3, 4} Sorgente
  • 52. Operazione di spostamento Commit University – 9 Aprile 2019, Firenze {1, 2, 3, 4} Destinazione {} Sorgente L’oggetto sorgente viene modificato!
  • 53. Sintassi di move? • Il C++ deliberatamente non introduce un nuovo operatore per lo spostamento • Lo spostamento può avvenire a seguito di una costruzione (costruttore di move) oppure di un assegnamento (move-assignement) • Ad esempio, data l’espressione di assegnamento: expr1 = expr2; il compilatore sceglierà automaticamente l’operazione giusta da fare • Come fa? Commit University – 9 Aprile 2019, Firenze
  • 54. Categorie di espressioni • Per ottenere questo risultato, il linguaggio C++ suddivide tutte le possibili espressioni in tre categorie principali, più due raggruppamenti • I due criteri fondamentali sono: • L’espressione rappresenta l’identità di un oggetto o no? • L’espressione è «disponibile» per l’operazione di move o no? Commit University – 9 Aprile 2019, Firenze
  • 55. Categorie di espressioni Espressioni che determinano l’identità di oggetti Espressioni non legate all’identità di oggetti Espressioni che NON consentono l’operazione di spostamento Espressioni che consentono l’operazione di spostamento Commit University – 9 Aprile 2019, Firenze
  • 56. Categorie di espressioni Espressioni che determinano l’identità di oggetti Espressioni non legate all’identità di oggetti Espressioni che NON consentono l’operazione di spostamento vec1 index array[2] obj.member Espressioni che consentono l’operazione di spostamento Commit University – 9 Aprile 2019, Firenze
  • 57. Categorie di espressioni Espressioni che determinano l’identità di oggetti Espressioni non legate all’identità di oggetti Espressioni che NON consentono l’operazione di spostamento vec1 index array[2] obj.member Espressioni che consentono l’operazione di spostamento 3.14156265 index + 42 makeVector() Commit University – 9 Aprile 2019, Firenze
  • 58. Categorie di espressioni Espressioni che determinano l’identità di oggetti Espressioni non legate all’identità di oggetti Espressioni che NON consentono l’operazione di spostamento vec1 index array[2] obj.member Espressioni che consentono l’operazione di spostamento 3.14156265 index + 42 makeVector() Commit University – 9 Aprile 2019, Firenze
  • 59. std::move • Per comunicare al compilatore la mia intenzione di rendere una variabile disponibile per lo spostamento uso la funzione di libreria apposita: std::move(vec2) • Utilizzando questa espressione a destra dell’operatore = si innesca l’operazione di spostamento vec1 = std::move(vec2); • Importante: non è la funzione std::move che fa lo spostamento, ma è l’operatore =! Commit University – 9 Aprile 2019, Firenze
  • 60. Categorie di espressioni Espressioni che determinano l’identità di oggetti Espressioni non legate all’identità di oggetti Espressioni che NON consentono l’operazione di spostamento vec1 *ptr array[2] obj.member Espressioni che consentono l’operazione di spostamento std::move(vec1) std::move(obj.member) std::move(obj).member 3.14156265 index + 42 makeVector() Commit University – 9 Aprile 2019, Firenze
  • 61. Espressioni che consentono l’operazione di spostamento rvalue Espressioni che determinano l’identità di un oggetto glvalue Recap: categorie di espressioni Commit University – 9 Aprile 2019, Firenze identità sì move no lvalue identità sì move sì xvalue identità no move sì prvalue
  • 62. Due cose su std::move • std::move non è una funzione «magica» • È solo il modo più semplice e conciso di trasformare un lvalue in un xvalue e quello che si usa nell’uso comune • Ci sono altre espressioni che appartengono alla categoria xvalue, ad esempio le chiamate a una funzione il cui valore di ritorno è una rvalue reference Commit University – 9 Aprile 2019, Firenze
  • 63. Costruttore di copia • Il tipico costruttore di copia, usato fin da C++98 ha questa forma: MyClass(const MyClass& x); • Il carattere & indica che x è un riferimento: il parametro x della funzione viene legato all’argomento con cui la funzione viene chiamata • In assenza del const, il riferimento non può legarsi a espressioni di categoria rvalue e per questo si chiama lvalue reference • La presenza di const invece permette al riferimento di legarsi a qualsiasi categoria di espressioni, in quanto il const garantisce che l’oggetto non verrà modificato Commit University – 9 Aprile 2019, Firenze
  • 64. Rvalue reference e costruttore di move • Il C++11 introduce il concetto di rvalue reference, ovvero un nuovo tipo riferimento che però lega unicamente con espressioni di categoria rvalue a mai con gli lvalue • Il costruttore di move prende quindi questa forma: MyClass(MyClass&& x); • A questo punto sarà il meccanismo di overloading a decidere quale costruttore utilizzare Commit University – 9 Aprile 2019, Firenze
  • 65. Overloading copy/move class MyClass { public: // copy ctor MyClass(const MyClass& x); // move ctor MyClass(MyClass&& x); /* ... */ }; MyClass makeClass(); Commit University – 9 Aprile 2019, Firenze MyClass c1; // lvalue -> copy ctor MyClass c2 { c1 }; // xvalue -> move ctor MyClass c3 { std::move(c1) }; // prvalue -> move ctor MyClass c4 { makeClass() }
  • 66. = delete struct MoveOnly { ~MoveOnly() {} // costruttore di move e move assignment MoveOnly(MoveOnly&&); MoveOnly& operator=(MoveOnly&&); // niente costruttore di copia e copy assignment MoveOnly(const MoveOnly&) = delete; MoveOnly& operator=(const MoveOnly&) = delete; }; Commit University – 9 Aprile 2019, Firenze • È possibile sopprimere una funzione dichiarandola con = delete
  • 67. Un caso speciale, l’istruzione return • Torniamo all’esempio da cui siamo partiti, ovvero: std::vector<int> makeVector() { std::vector<int> vec; /* popola v con valori */ return vec; // vec è un lvalue! } • Questo è l’unico caso in cui al compilatore è concesso di convertire implicitamente da lvalue a xvalue, quindi non è necessario usare std::move, verrà usato lo spostamento comunque se possibile Commit University – 9 Aprile 2019, Firenze
  • 68. Impatto della move semantic • A prima vista, sembra che l’effetto della move semantic si limiti ad una ottimizzazione, mediante l’eliminazione di copie ridondanti • In verità ha effetti più profondi sullo stile di programmazione: • Consente interfacce più naturali, evitando il ricorso ai parametri «in sola uscita» • Consente l’introduzione di tipi move-only per rappresentare in maniera espressiva quelle risorse che, dal punto di vista semantico, non possono essere copiate Commit University – 9 Aprile 2019, Firenze
  • 69. Smart Pointer Commit University – 9 Aprile 2019, Firenze
  • 70. Smart pointer • Gli smart pointer sono template di classi che consentono di: • Liberarsi dalla schiavitù del new/delete (in particolare, del delete) • Scrivere più facilmente codice privo di memory-leak, in particolare se si usano le eccezioni • Scrivere codice più “espressivo” che consenta non solo al compilatore, ma anche all’umano di capire al volo l’intento del programmatore • Implementare tecniche particolari, ad es. l’idioma “pimpl” Commit University – 9 Aprile 2019, Firenze
  • 71. Custodia • Come per l’esempio dei vettori visto prima, ogni volta che viene allocata memoria dinamicamente sorge il problema della custodia, ovvero: chi si prende la responsabilità di pulire quando abbiamo finito? • Utilizzando i puntatori “nudi” alla C, il compilatore non ha alcuna informazione sulla custodia e quindi tutto è a carico del programmatore che deve tenerne conto e documentarla • Gli smart pointers invece incapsulano il concetto di custodia e questo consente al compilatore di generare il codice corretto automaticamente Commit University – 9 Aprile 2019, Firenze
  • 72. std::unique_ptr<T> • unique_ptr rappresenta un puntatore con custodia esclusiva: sono io e solo io che ho la responsabilità di deallocare • Uno unique_ptr non può essere copiato, altrimenti la custodia non sarebbe più esclusiva! • Però può essere spostato, in quel caso la custodia si sposta assieme al puntatore • Ecco uno dei tipi move-only di cui parlavamo prima! Commit University – 9 Aprile 2019, Firenze
  • 73. Metodi di std::unique_ptr<T> • Se ptr è un unique_ptr<T> le operazioni più comuni sono: *ptr // dereferenzia il puntatore ptr->m // dereferenzia il puntatore if (ptr) // controlla se il puntatore è nullo ptr.get() // ottiene il puntatore nudo ptr.reset(); // rilascia la memoria e azzera ptr.release(); // come get() + reset() Commit University – 9 Aprile 2019, Firenze
  • 74. Piccolo quiz! • Dati due std::unique_ptr, ptr1 e ptr2 questa istruzione: ptr1 = ptr2; causa un errore di compilazione, perché unique_ptr è un tipo move-only! • Se invece scrivo così: ptr1 = std::move(ptr2); allora compila correttamente e l’effetto è di: • Rilasciare la memoria gestita da ptr1 • Passare la custodia della memoria di ptr2 in ptr1 • Azzerare ptr2 Commit University – 9 Aprile 2019, Firenze
  • 75. std::unique_ptr<T, D> • Per default, il distruttore di unique_ptr usa sempre l’operatore delete per deallocare il puntatore, quindi funziona solo se il puntatore sia stato ottenuto con una chiamata a new • Se questo non è il vostro caso, potete specificare un custom deleter, che viene invocato al posto di delete • Il deleter viene passato come argomento template e quindi fa parte del tipo del puntatore Commit University – 9 Aprile 2019, Firenze
  • 76. Piccola parentesi: nullptr • In C++11 viene introdotto nullptr, che agisce come puntatore nullo “universale” in sostituzione della macro NULL ereditata dal C • L’espressione nullptr ha il tipo a sè stante std::nullptr_t che può essere convertito a qualsiasi puntatore, questo rende più semplice l’interazione con i template • Perciò possiamo scrivere, indipendentemente dal tipo di puntatore ptr = nullptr; Commit University – 9 Aprile 2019, Firenze
  • 77. std::shared_ptr • shared_ptr rappresenta un puntatore con custodia condivisa • Quando viene copiato, la custodia viene condivisa tra le due copie • L’oggetto puntato viene deallocato solo quando l’ultimo oggetto che ha la custodia viene distrutto • L’effetto è ottenuto tramite la tecnica del reference count Commit University – 9 Aprile 2019, Firenze
  • 78. Metodi di std:: shared_ptr<T> • Se ptr è un shared_ptr<T> le operazioni più comuni sono: *ptr // dereferenzia il puntatore ptr->m // dereferenzia il puntatore if (ptr) // controlla se il puntatore è nullo ptr.get() // ottiene il puntatore nudo ptr.reset(); // rilascia la custodia e azzera • Cioè, praticamente, le stesse di unique_ptr con l’eccezione di release che non ha senso per uno shared_ptr Commit University – 9 Aprile 2019, Firenze
  • 79. Altro piccolo quiz ptr1 = ptr2; • Compila, l’effetto è: • Rilascia la custodia di ptr1 • Assegna a ptr1 una custodia della risorsa gestita da ptr2 condivisa con ptr2 Commit University – 9 Aprile 2019, Firenze ptr1 = std::move(ptr2); • Compila, l’effetto è: • Rilascia la custodia di ptr1 • Assegna a ptr1 la custodia della risorsa gestita da ptr2 • Azzera ptr2 • Dati due std::shared_ptr, ptr1 e ptr2
  • 80. Custom deleter • Anche nel caso di shared_ptr, il distruttore per default chiama delete, ma è possibile specificare un custom deleter • Al contrario di unique_ptr, però, il custom deleter non fa parte del tipo e viene memorizzato nel puntatore stesso con la tecnica del type erasing • Questo consente l’utilizzo di tipi opachi e il cosidetto idioma pimpl Commit University – 9 Aprile 2019, Firenze
  • 81. std::weak_ptr • weak_ptr è un osservatore di shared_ptr, ovvero mantiene un riferimento ad un oggetto gestito tramite shared_ptr ma non partecipa alla custodia • Dato un weak_ptr è possibile sapere se l’oggetto è ancora disponibile o se è stato rilasciato • Se è ancora disponibile, si può convertire un weak_ptr in uno shared_ptr per ottenere la custodia dell’oggetto Commit University – 9 Aprile 2019, Firenze
  • 82. Uso degli smart pointer void fun() { std::unique_ptr<Type> ptr { new Type() }; // non è necessario alcun delete } Commit University – 9 Aprile 2019, Firenze • Gli smart pointer semplificano il codice perché ci liberano dalla necessità del delete, anche in presenza di eccezioni
  • 83. Uso degli smart pointer void fun() { // make_unique evita persino il new auto ptr = std::make_unique<Type>(); // non è necessario alcun delete } Commit University – 9 Aprile 2019, Firenze • Da C++14, possiamo addirittura fare a meno del new
  • 84. Uso degli smart pointer class MyClass { std::unique_ptr<Type> ptr; public: // non è necessario scrivere un distruttore esplicito // perché il distruttore di unique_ptr si occupa di tutto } Commit University – 9 Aprile 2019, Firenze • Possiamo evitare di scrivere distruttori
  • 85. Smart pointer e contenitori // vettore di puntatori a custodia unica std::vector<std::unique_ptr<Type>> vec_unique; // vettore di puntatori a custodia condivisa std::vector<std::shared_ptr<Type>> vec_shared; Commit University – 9 Aprile 2019, Firenze • Possiamo mettere in sicurezza puntatori nei nostri contenitori, certi che la memoria verrà deallocata correttamente
  • 86. Interfacce e smart pointer // ma chi dealloca??? MyType* createObject(); Commit University – 9 Aprile 2019, Firenze • Con gli smart pointer, è possibile scrivere interfacce più espressive
  • 87. Interfacce e smart pointer // ma chi dealloca??? MyType* createObject(); Commit University – 9 Aprile 2019, Firenze // dealloca il chiamante std::unique_ptr<MyType> createObject(); // la custodia è condivisa std::shared_ptr<MyType> createObject(); • Con gli smart pointer, è possibile scrivere interfacce più espressive
  • 88. Oggetti funzione e espressioni lambda Commit University – 9 Aprile 2019, Firenze
  • 89. Passare “pezzi di codice” • Vi sarà sicuramente capitata l’esigenza di passare come parametro di una funzione un «pezzo di codice», per esempio: • Usando un algoritmo della libreria standard • Usando una funzione che richiede una callback • ecc. … Commit University – 9 Aprile 2019, Firenze
  • 90. Puntatori a funzione void increment(int& i) { ++i; } void f(std::vector<int>& v) { std::for_each(v.begin(), v.end(), increment); } Commit University – 9 Aprile 2019, Firenze
  • 91. Function object struct Increment { void operator()(int& i) { ++i; } }; void f(std::vector<int>& v) { std::for_each(v.begin(), v.end(), Increment{}); } Commit University – 9 Aprile 2019, Firenze
  • 92. Function object struct IsEven { bool operator()(int i) { return i % 2 == 0; } }; void f(std::vector<int>& v) { auto it = std::find_if(v.begin(), v.end(), IsEven{}); } Commit University – 9 Aprile 2019, Firenze
  • 93. Function object vs. puntatori a funzione • Il principale vantaggio di un function rispetto ad un puntatore a funzione è che il function object è un tipo • Questo significa che: • Partecipa al meccanismo dei template • Consente la sostituzione inline della funzione • Consente la creazione di oggetti stateful Commit University – 9 Aprile 2019, Firenze
  • 94. Stateful function object struct IncrementBy { int delta; void operator()(int& i) { i += delta; } }; void func(std::vector<int>& v) { std::for_each(v.begin(), v.end(), IncrementBy{42}); } Commit University – 9 Aprile 2019, Firenze
  • 95. Stateful function object struct IncrementBy { int delta; void operator()(int& i) { i += delta; } }; void func(std::vector<int>& v, int delta) { std::for_each(v.begin(), v.end(), IncrementBy{delta}); } Commit University – 9 Aprile 2019, Firenze
  • 96. I function object sono scomodi • Scrivere function object è scomodo: • Bisogna scrivere linee di codice inutili • Bisogna inventarsi un nome che abbia senso • Il codice del function object è «fisicamente» lontano dal punto in cui l’oggetto viene effettivamente utilizzato • Il fastidio è maggiore se il function object viene utilizzato una sola volta, in un punto specifico del programma Commit University – 9 Aprile 2019, Firenze
  • 97. Lambda expression void func(std::vector<int>& v, int delta) { std::for_each(v.begin(), v.end(), [delta](int& i) { i += delta; }); } Commit University – 9 Aprile 2019, Firenze
  • 98. Lambda expression • Una lambda expression è un modo conciso e pratico per scrivere un function object • Per ogni lambda expression il compilatore: • Crea un tipo chiusura o closure type, ovvero una classe con un nome unico generato automaticamente che implementa il function object descritto nell’espressione • Quindi, sostituisce all’espressione un prvalue ottenuto mediante costruendo un oggetto del closure type Commit University – 9 Aprile 2019, Firenze
  • 99. Closure type struct lambda_10f32efa9bc { int delta; void operator()(int& i) const { i += delta; } }; void f(std::vector<int>& v, int delta) { std::for_each(v.begin(), v.end(), lambda_10f32efa9bc {delta}); } Commit University – 9 Aprile 2019, Firenze
  • 100. { }corpo( )parametri specif Sintassi di una lambda expression [ ]catture Questa parte è opzionale! Commit University – 9 Aprile 2019, Firenze
  • 101. { }corpo ( )parametri specif Sintassi di una lambda expression [ ]catture class lambda_10f32efa9bc { catture public: auto operator()(param) specif const { corpo } }; Commit University – 9 Aprile 2019, Firenze
  • 102. Catture (Captures) • Le catture servono per «catturare» valori e variabili locali dal contesto in cui si trova la lambda expression • Possiamo anche non catturare nulla con [], in quel caso il closure type è stateless • Possiamo catturare per valore o per riferimento Commit University – 9 Aprile 2019, Firenze
  • 103. Esempi di catture // nessuna cattura Button b; b.OnClick([] { exit(); }); Commit University – 9 Aprile 2019, Firenze
  • 104. Esempi di catture // cattura per valore Button b; Color col; b.OnClick([col] { SetWindowColor(col); }); Commit University – 9 Aprile 2019, Firenze
  • 105. Esempi di catture // cattura per riferimento Button b; Checkbox c; b.OnClick([&c] { c.Toggle(); }); Commit University – 9 Aprile 2019, Firenze
  • 106. Esempi di catture // cattura per riferimento e per valore Button b; Checkbox c; Color col; b.OnClick([&c, col] { c.SetColor(col); }); Commit University – 9 Aprile 2019, Firenze
  • 107. Capture-default “=” Button b; Checkbox c; Color col; // con =, tutte le variabili vengono catturate per valore b.OnClick([=] { SetWindowColor(col); }); // ... tranne quelle indicate esplicitamente b.OnClick([=, &c] { c.SetColor(col); }); Commit University – 9 Aprile 2019, Firenze
  • 108. Capture-default “&” Button b; Checkbox c; Color col; // con &, tutte le variabili vengono catturate per riferimento b.OnClick([&] { c.Toggle(); }); // ... tranne quelle indicate esplicitamente b.OnClick([&, col] { c.SetColor(col); }); Commit University – 9 Aprile 2019, Firenze
  • 109. Catturare “this” • Se siamo in una funzione membro di una classe, è possibile anche catturare il parametro nascosto this, questo permette di utilizzare nel corpo della lambda i membri della classe (funzioni e campi non statici) Commit University – 9 Aprile 2019, Firenze
  • 110. Catturare “this” class Checbox { void Toggle(); std::string label; // ... }; void Checkbox::Create(Button& b) { b.OnClick([this] { Toggle(); }); // this->Toggle() b.OnDoubleClick([this] { Print(label); }); // this->label } Commit University – 9 Aprile 2019, Firenze
  • 111. Catturare “this” • Se è specificato il capture-default [&] allora this viene automaticamente aggiunto alla lista delle catture se è necessario • Attenzione: [this] cattura l’oggetto implicito (e tutti i suoi membri) per riferimento! • Da C++17 in poi, è possibile anche catturare l’oggetto implicito per valore, utilizzando [*this] Commit University – 9 Aprile 2019, Firenze
  • 112. Catturare per riferimento void f() { Checkbox c; b.OnClick([&] { c.Toggle(); }); // ops! c viene distrutto qui! } • Attenzione quando catturate per riferimento, perché è vostra responsabilità garantire che l’oggetto sia ancora valido quando la lambda sarà invocata! Commit University – 9 Aprile 2019, Firenze
  • 113. Cosa possiamo catturare? • Oltre a this, solo le variabili locali, non statiche, non thread_local possono essere catturate • Variabili globali, statiche e thread_local possono essere usate senza bisogno di catturarle, facendo però attenzione che la variabile sarà usata direttamente, in quanto non fa parte della chiusura • Variabili locali constexpr possono essere usate senza bisogno di catturarle, in quanto il loro valore è noto a compile-time e può essere sostituito inline Commit University – 9 Aprile 2019, Firenze
  • 114. Cattura e move-semantic • Quando una variabile è catturata per copia, viene creato un membro dato nella chiusura per ospitarne il valore, quindi l’espressione lambda inizializza tale membro copiando la variabile • In C++11 non è possibile catturare oggetti utilizzando il costruttore di move, quindi non si possono catturare oggetti move-only • Vedremo come questo è stato reso possibile a partire da C++14 Commit University – 9 Aprile 2019, Firenze
  • 115. Espressioni Lambda in C++14 • Oltre alle variabili è possibile catturare anche il valore di una espressione: [x = expr] { /* x cattura il valore di expr */ } • Questo consente la cattura per move anziché per copy: [x = std::move(x)] { /* x catturato con move-ctor */ } • Fate attenzione che la prima x dà il nome alla cattura, mentre la seconda x si riferisce ad una variabile nel contesto della funzione lambda Commit University – 9 Aprile 2019, Firenze
  • 116. Tipo del valore di ritorno [] (int& i) { i += 1; } Il valore di ritorno dell’operatore () è void [] (int& i) { return i + 1; } Il valore di ritorno dell’operatore () è int • Grazie ad auto nella definizione dell’operatore (), non è necessario specificare il valore di ritorno, che viene dedotto dal corpo della lambda expression Commit University – 9 Aprile 2019, Firenze
  • 117. Tipo del valore di ritorno • Nei casi in cui è utile o necessario specificare un tipo esplicitamente, si può usare una normale clausola trailing-return-type auto operator()() -> float const { return 1; } []() -> float { return 1; } Commit University – 9 Aprile 2019, Firenze
  • 118. struct lambda_10f32efa9bc { catture auto operator()(param) specif const { corpo } }; const o non const? • A causa del const aggiunto automaticamente, modificare i campi della chiusura causa un errore di compilazione Commit University – 9 Aprile 2019, Firenze
  • 119. const per default int f(std::vector<int> v) { int count = 0; std::for_each(v.begin(), v.end(), [=](int i){ if (i % 2 == 0) ++count; }); return count; } Commit University – 9 Aprile 2019, Firenze
  • 120. A qualcuno piace mutable void f(std::vector<int> v) { int index = 0; std::for_each(v.begin(), v.end(), [=](int i) mutable { ++index; if (i % 2 == 0) std::cout << index; }); } La keyword mutable sopprime const Commit University – 9 Aprile 2019, Firenze
  • 121. Polymorphic lambda [](auto x) { /* ... */ } struct lambda_10f32efa9bc { template <class T> auto operator()(T x) const { /* ... */ } }; • È possibile dichiarare i parametri dell’espressione lambda con la keyword, di fatto l’operatore () diventa un template! Commit University – 9 Aprile 2019, Firenze
  • 122. Memorizzare oggetti funzione • Dato che ogni espressione lambda ha un suo tipo unico, il cui nome è generato automaticamente, se volete memorizzare una lambda in una variabile, potete usare auto: auto x = [](){}; • Per passare una lambda come parametro di una funzione, potete sempre usare un parametro template: template <class Fun> void f(Fun f); Commit University – 9 Aprile 2019, Firenze
  • 123. std::function std::function<void ()> f = [](){}; std::function<void ()> r = rand; std::function<void (int&)> i = Increment{}; std::function<void (int&)> j = IncrementBy{10}; std::function<int (float)> g = [](float f) { return int(floor(f)); }; std::function<int (int)> h = [mult](int x) { return mult * x; }; Commit University – 9 Aprile 2019, Firenze • Un’alternativa è usare il template di libreria std::function • Grazie ad una tecnica chiamata type erasure un variabile di tipo std::function può contenere qualsiasi oggetto funzione con una determinata firma
  • 124. Attributi Commit University – 9 Aprile 2019, Firenze
  • 125. Attributi • Spesso i compilatori hanno l’esigenza di aggiungere delle estensioni al linguaggio (ad e.s.: __declspec, __attribute__, ecc.) • Per ovviare al proliferare di sintassi proprietarie e non-compatibili, sono stati introdotti gli attributi • La grammatica consente di piazzare attributi in moltissimi posti, per poterli “attaccare” a variabili, tipi, funzioni, semplici istruzioni, ecc. Commit University – 9 Aprile 2019, Firenze
  • 126. Attributi • Le sintassi di un attributo sono: • [[attributo]] • [[attributo(parametri)]] • [[namespace::attributo]] • [[namespace::attributo(parametri)]] • Gli attributi con namespace sono riservati per le estensioni proprietarie dei compilatori Commit University – 9 Aprile 2019, Firenze
  • 127. Attributi • Gli attributi possono, ad esempio, essere utilizzati per fornire informazioni aggiuntive al compilatore al fine di: • Generare o sopprimere messaggi diagnostici • Ottimizzare il codice • Ottenere effetti dipendenti dal compilatore non previsti dallo standard • Al compilatore è consentito di ignorare totalmente un attributo Commit University – 9 Aprile 2019, Firenze
  • 128. deprecated [[deprecated(“usare l’overload col parametro param”)]] void fun(); void fun(Type param); Commit University – 9 Aprile 2019, Firenze • L’attributo deprecated indica che una entità (variabile, tipo, funzione, enumeratore, ecc.) non dovrebbe essere usato perché obsoleto
  • 129. fallthrough switch(expr) { case 0: printf(“0”); [[fallthrough]]; case 1: printf(“1”); break; } Commit University – 9 Aprile 2019, Firenze • L’attributo fallthrough serve per dire al compilatore (e anche ai vostri colleghi umani!) che avete deliberatamente omesso un break in uno switch
  • 130. nodiscard struct [[nodiscard]] error_info { /* ... */ }; error_info enable_missile_safety_mode(); void launch_missiles(); void test_missiles() { enable_missile_safety_mode(); // warning! launch_missiles(); } Commit University – 9 Aprile 2019, Firenze • L’attributo nodiscard serve per indicare che il valore di ritorno di una funzione non deve essere ignorato
  • 131. <chrono> Commit University – 9 Aprile 2019, Firenze
  • 132. <chrono> • <chrono> è la libreria per il calcolo di quantità temporali • Sfrutta la meta-programmazione per garantire la massima precisione, assenza di errori di arrotondamento e evitare gli errori di conversione • Si appoggia alla libreria <ratio> che fornisce dei tipi per rappresentare le frazioni intere Commit University – 9 Aprile 2019, Firenze
  • 133. std::duration<Rep, Period> • Il template duration serve per rappresentare una durata di tempo • Rep è un tipo numerico (intero o floating point) • Period è un’istanza di ratio<> • Il valore viene memorizzato in una variabile di tipo Rep e l’unità di misura è data dal Period misurato in secondi Commit University – 9 Aprile 2019, Firenze
  • 134. Tipi definiti per comodità d’uso using nanoseconds = duration<int64_t, nano>; using microseconds = duration<int64_t, micro>; using milliseconds = duration<int64_t, milli>; using seconds = duration<int64_t>; using minutes = duration<int32_t, ratio<60>>; using hours = duration<int32_t, ratio<3600>>; Commit University – 9 Aprile 2019, Firenze
  • 135. Conversione automatica hours h { 2 }; // 2 ore minutes m { 20 }; // 20 minuti minutes t1 = h + m; // 140 minuti hours t2 = h + m; // errore! perdita di precisione Commit University – 9 Aprile 2019, Firenze • Le normali operazioni tra oggetti duration effettuano automaticamente la conversione quando possibile
  • 136. std::time_point<Clock, Duration> • Il template time_point rappresenta invece un punto specifico nella linea temporale di uno specifico orologio • Clock è una classe che soddisfa ai requisiti di «orologio» • Duration è una instanza di duration<> che viene usato internamente per rappresentare il tempo trascorso tra il punto rappresentato e l’origine temporale dell’orologio (epoch) Commit University – 9 Aprile 2019, Firenze
  • 137. Orologi • Lo standard definisce tre orologi: • system_clock: l’orologio di sistema (quello della libreria C) • steady_clock: un orologio che va sempre e solo in avanti in maniera costante • high_resolution_clock: l’orologio che ha la maggior risoluzione disponibile • Ogni orologio ha un metodo now() che ritorna il time_point attuale • È possibile scrivere facilmente un orologio custom Commit University – 9 Aprile 2019, Firenze
  • 138. Operazioni tra tipi di <chrono> duration + duration duration duration - duration duration time_point + duration time_point time_point - duration time_point time_point + time_point Errore! time_point - time_point duration se i due time point sono dello stesso orologio, altrimenti errore! Commit University – 9 Aprile 2019, Firenze
  • 139. Uso di <chrono> // Platform.h void Wait(int milliseconds); // Main.cpp Wait(1000); // quanto tempo? Commit University – 9 Aprile 2019, Firenze // Platform.h void Wait(milliseconds delay); // Main.cpp Wait(1000ms); // Ahh! Sono 1000 // millisecondi! Wait(1s); // Stessa cosa, 1s // viene convertito // automaticamente • I tipi chrono possono essere usati per migliorare le interfacce che hanno a che fare col tempo
  • 140. Il futuro è C++20 Commit University – 9 Aprile 2019, Firenze
  • 141. C++20 • La C++ Committee sta lavorando sodo alla bozza del C++20 che è già in feature freeze, le principali novità sono già consolidate • Da qui alla pubblicazione, prevista per il 2020, si tratta unicamente di piccole aggiunte o aggiustamenti • Il C++20 promette un nuovo salto di qualità, con l’introduzione di cambiamenti di ampio respiro che avranno un impatto significativo sull’intero ecosistema di C++ e sullo stile di programmazione Commit University – 9 Aprile 2019, Firenze
  • 142. // C++03/11/14/17 #include <iostream> int main() { std::cout << "hello, worldn"; } // C++20 import std.io; int main() { std::cout << "hello, worldn"; } Moduli L’introduzione dei moduli elimina finalmente la fastidiosa dipendenza dal preprocessore C per la gestione degli #include I moduli consentono l’implementazione di toolchain più ottimizzate e promuovono una migliore separazione dei componenti nei progetti di grandi dimensioni Commit University – 9 Aprile 2019, Firenze
  • 143. task<> ReadData() { auto conn = co_await Connect(); auto data = co_await conn->Read(); auto response = MakeResponse(data); co_await conn->Write(response); } generator<int> fibonacci(int n) { int i0 = 1, i1 = 1; for (;;) { co_yield i0; int in = i0 + i1; i0 = i1; i1 = in; } } Coroutine Le coroutine consentono di scrivere funzioni che possono essere sospese e riprese dal punto di interruzione Consentono di semplificare notevolmente il codice asincrono e di implementare i generatori lazy C++20 non vincola ad uno specifico runtime, ma fornisce un supporto «aperto» alle personalizzazioni Commit University – 9 Aprile 2019, Firenze
  • 144. float sqrt(float x) [[pre: x >= 0]]; std::unique_ptr<Type> makeObj() [[post ret: ret.get() != nullptr]]; void question(int x) { [[assert: x != 42]]; } Contratti I contratti introducono il supporto per il cosiddetto design by contract Consentono di scrivere pre- condizioni, post-condizioni e asserzioni in maniera integrata nel linguaggio, senza l’utilizzo di macro Commit University – 9 Aprile 2019, Firenze
  • 145. // C++03/11/14/17 template <class RanIt> void sort(RanIt begin, RanIt end); // C++20 template <RandomIterator It> void sort(It begin, It end); template <RandomAccessRange Range> void sort(Range& range); Concetti I concetti migliorano ulteriormente il sistema dei template, introducendo la possibilità di esprimere vincoli sui parametri template, verificati prima di istanziare l’intero template Questo migliorerà i messaggi diagnostici del codice che usa i template e consentirà nuove tecniche di meta-programmazione, meno onerose per il compilatore Commit University – 9 Aprile 2019, Firenze
  • 146. // C++03/11/14/17 template <class T> void func(T x) {} // C++20 void func(auto x) {} Template I template ottengono una nuova sintassi abbreviata, derivata da quelle delle polimorphic lambda di C++14 Sarà quindi possibile scrivere una funzione template… senza usare la parola chiave template! Commit University – 9 Aprile 2019, Firenze
  • 147. std::vector<int> read_data(); std::vector<int> vi = read_data() | action::sort | action::unique; <range> La libreria <range> un approccio «moderno» agli algoritmi che sposta l’attenzione dagli iteratori ai range: coppie di iteratori che rappresentano una sequenza iterabile di oggetti La caratteristica peculiare è la componibilità e riutilizzo del codice Commit University – 9 Aprile 2019, Firenze
  • 148. Range • <range> è basato sulla libreria open source Ranges v3, a sua volta evoluzione di Boost.Ranges • Per chi volesse approfondire, la libreria Ranges v3, che è compatibile con C++11/14/17, è scaricabile qui: https://github.com/ericniebler/range-v3 ed è inclusa nell’ultima distribuzione di Visual Studio • La versione C++20 utilizza i concetti, ma le differenze finiscono lì Commit University – 9 Aprile 2019, Firenze