C++11
Michelangelo Diligenti Ingegneria Informatica e
dell'Informazione
diligmic@dii.unisi.it
C++11
●
Nuova revisione dello standard dopo il C++98
– Stabilita nel 2011 dopo anni di lavoro
– I compilatori la supportano da non molto tempo
●
Tantissime novita'
– Nuove librerie
– Nuove features
– Cambia molto l'uso del linguaggio anche in cose fondamentali come il ritorno dei valori
●
Copriremo solo alcuni aspetti
– Per un testo completo vedete risorse online o Effective Modern C++, Scott Meyers
Auto
●
Deduzione automatica di tipo, esempi
– auto x = 0; // x intero auto x = 0.0; // x double
auto x; // ERRORE tipo non determinabile
– map<string, int> mappa;
auto iter = mappa.begin(); // prendo iteratore
– const map<string, int> mappa;
auto iter = mappa.begin(); // prendo iteratore const
– Posso unire a const e references per forzare constness od assicurare la copia per riferimento const auto& nome = oggetto.GetNome();
Range loops
●
Possibile definire iterazione su contenitori senza usare gli iteratori
vector<int> v;
for (int i : v) { cout << i << endl; }
map<string, int> m;
for (std::pair<string, int> p : m) {
cout << p.first << “:“ << p.second << endl;
}
Range loops
●
Range loop spesso usati insieme ad auto
vector<int> v;
// accesso per valore, i e' copia dei valori nel vettore for (auto i : v) {}
map<string, int> m;
// p passato per valore, p e' copia del pair<string, int>
for (auto p : m) {
cout << p.first << “ “ << p.second << endl;
}
Range loops
●
Auto permette grande flessibilita', ad esempio posso forzare constness e/o copia per riferimento
vector<int> v;
for (const auto& i : v) {...} // i adesso e' const int&
map<string, int> m;
// Accesso per const reference a std::pair<const std::string, int>
for (const auto& p : m) {
cout << p.first << “ “ << p.second << endl;
}
Braced initialization
● C++98 inconsistente nella gestione delle inizializzazioni int i = 0;
int i(0); // come sopra vector<int> v(10);
vector<int> v = vector<int>(10); // come sopra
● C++11 possibile inizializzare gli oggetti in modo consistente tramite {}
int i{0};
vector<int> v{10};
// Array const con 5 elementi subito inizializzati const float* vec = new const float[5]{1,2,3,4,5};
Braced initialization
●
Permette di fare anche inizializzazioni complesse
●
Liste di parametri nel caso generale (vi rimando al testo per questo)
●
Le seguenti sono inizializzazioni valide in C++11
– Inizializzare un vettore di 5 interi vector<int> v = {3,2,1,5,4};
– mappa con 2 valori iniziali
map<string,int> m = {{"C++98",1998}, {"C++11",i}};
nullptr vs NULL
●
In C++98 NULL e' semplicemente
#define NULL 0
– NULL puo' essere interpretato come un intero
● Non ha un tipo esplicito
● Il seguente codice compila (al massimo puo' dare un warning il compilatore)
int i = NULL + 4;
●
C++11 specifica nullptr che non e' convertibile in un intero
– Si usa come NULL Class * c = nullptr;
if (c == nullptr) { … }
●
Deleted methods
●
In C++98 si evita la copia di un oggetto definendo costruttori di copia e operator= come privati
private:
Pippo(const Pippo&) {}
Pippo& operator=(const Pippo&) {} …
●
In C++11 si specifica che tali metodi sono deleted
public:
Pippo(const Pippo&) = delete;
Pippo& operator=(const Pippo&) = delete;
Default methods
●
Possibile dire al compilatore in modo esplicito quali metodi esso deve generare
– Applicabile a default, copy e move (vederemo cosa sia) construtor, destructor, operator=
class Pippo {
Pippo(int a) { … }
// Default constructor non sarebbe generato dal
// compilatore perche' c'e' Pippo(int), ma dico di generarlo
Pippo() = default;
Pippo(const Pippo& p) = default;
};
Constructor Delegation
●
In C++98 si puo' invocate un costruttore del padre in pre-run, ma
– Non si puo' invocare altro costruttore della classe
– Problema: Non si riusa codice dei costruttori
●
In C++11 possibile chiamare pre-run altri costruttori
– Riutilizzo del codice possibile anche per i costruttori class Pippo {
Pippo(const int a) { … } Pippo() : Pippo(0) {…}
Pippo(const float b) : Pippo(static_cast<int>(b)) {…}
};
Inizializzazione in-class
●
Possibile inizializzare i dati membri in fase di
dichiarazione senza dover passare dal costruttore
– Finalmente! Codice chiaro, leggibile e sicuro
– Non rimane mai qualcosa di non inizializzato class Pippo {
string* s = nullptr;
const static int x = 5;
int y = 5; // o int y{5};
vector<int> myVec{1,2,3,4,5};
};
Lambda functions
●
Funzioni senza nome create nel posto dove servono
●
Esempio sort vettore in C++98
struct Comparator {
bool operator()(int v, int w){ return v > w; } };
sort(vec.begin(), vec.end(), Comparator());
●
In C++11 con lambda function:
sort(vec.begin(),vec.end(), [](int v,int w){ return v > w; });
Lambda functions
●
Lambda function: sintassi
– [variabili_catturate](argomenti) { … la funzione … }
● Variabili catturate sono le variabili locali nel chiamante che sono usate nella lambda
● Default passate per valore. Possibile passarle per reference per prendere il risultato: Esempio:
vector<int> v{1,2,3,4,5};
int sum = 0;
// sum passata per reference dal contesto locale. Valore di sum dopo la // chiamata = 15 (1+2+3+4+5)
for_each(v.begin(),v.end(),[&sum](int v){sum += v;});
// sum passato per copia, suo valore resta 0 esternamente for_each(v.begin(),v.end(),[sum](int v){sum += v;});
Move semantics
●
Passo fondamentale del C++11
– Evita il problema del C++98 della copia dei dati locali di una funzione o metodo!
●
Esempio ritornare una matrice grande
– C++98
● Matrix* Alloca(); // puntatore, poi devo deallocare
● void Alloca(Matrix*); // OK ma poco naturale
– C++11
● Matrix Alloca(); // posso evitare la copia!
●
Move semantic permette di definire come spostare
un dato da una variabile temporanea ad un'altra
Move semantics: lvalue, rvalue
●
lvalue: valore di cui posso prendere indirizzo
– permanente rispetto all'espressione valutata
●
rvalue: valore che esiste solo durante la valutazione dell'espressione
●
Esempio 1:
int x = 1; // x lvalue, 1 rvalue
Move semantics: lvalue, rvalue
●
Esempio 2:
Matrix Alloca() {
Matrix m(100,100);
return m; // m e' variabile temporanea, rvalue }
●
Esempio 3
static Matrix m;
Matrix Alloca() { m.Init(100, 100);
return m; // m statica e permanente, lvalue }
Move semantics: reference &
●
Reference classica & e' applicabile solo a lvalues
– Matrix& Alloca() { Matrix m(100,100);
// disastro in vista, riferimento a rvalue che viene distrutto
return m;
}
– static Matrix m;
Matrix& Alloca() { m.Init(100, 100);
return m; // m statica e permanente, riferimento OK }
Move semantics: &&, move constructor
●
Reference && solo applicabile ad rvalues
– Posso specificare una specie di copy constructor per rvalues
– rvalue e' temporaneo non serve fare una vera operazione di copia ma solo di spostamento!
● Per oggetti grandi tale operazione e' spesso molto piu' rapida
●
Move constructor: invocato se si passa per valore un rvalue
class Pippo {
Pippo(const Pippo& p) { … } // copy constructor Pippo(Pippo&& p) { … } // move constructor
};
Move semantics: &&, move constructor
●
Esempio di move constructor
class Matrix { …
int N, M;
float* data;
Matrix(Matrix&& m) { // move constructor N=m.N; M=m.M;
// La rappresentazione dell'rvalue viene “rubata”, e si // riutilizza la memoria precedentemente allocata.
data = m.data;
m.data = nullptr; m.N=m.M=0; // svuotare il vecchio matrix }
};
– Esempio di utilizzo
Matrix Alloca() {
Matrix m(100, 100);
return m; } Matrix m = Alloca();
Si ritorna l'rvalue per valore, compilatore invoca il move constructor. Massima velocita' e pulizia dell'interfaccia.
Move semantics: &&, move constructor
●
Move constructor veloce vs Copy constructor lento
class Matrix { …
Matrix(Matrix&& m) { // move constructor N=m.N; M=m.M;
// La rappresentazione dell'rvalue viene “rubata”, si copia // solo 1 puntatore data = m.data; m.data=nullptr; m.M=m.N=0;
}
Matrix(const Matrix& m) { // copy constructor N=m.N; M=m.M;
// Assumo implementazione con singolo vettore contiguo data = new float[N*M]; // devo riallocare, lento!
for (int i = 0; i < N*M; ++i) // copia elemento ad elemento data[i] = m.data[i];
} ...
Move semantics: contenitori
● Contenitori stl copiano in inserimento
– std::move() trasforma una lvalue in un rvalue
● Permette di chiamare move semantics in inserimento!
– C++98
vector<string> v;
string s(“ciao”);
v.push_back(s); // v prende una copia di s cout << s; // stampa “ciao”
– C++11: Move semantics permette di evitare la copia
vector<string> v;
string s(“ciao”);
v.push_back(std::move(s)); // s “rubata” dal contenitore
cout << s; // stampa “”, la rappresentazione di s e' stata rubata ed e' in v
std::move(s) crea string&&
Compilatore invoca il move constructor
Move semantics: &&, move constructor
●
std::move usabile in ogni contesto ad esempio nel caso precedente di move constructor
class Matrix { …
int N, M;
float* data;
Matrix(Matrix&& m) : data(std::move(m.data)) { // move constructor N=m.N; M=m.M;
m.data=nullptr;
m.N=m.M=0;
} };
Librerie: smart pointers
●
std::unique_ptr puntatore con ownership unica
#include <memory>
{
std::unique_ptr<int> ptr(new int(42));
std::cout << ptr.get() << std::endl; // indirizzo std::cout << *ptr << std::endl; // 42
std::unique_ptr<int> second = first; // compile error // ptr distrutto automaticamente qui
}
Librerie: smart pointers
●
std::shared_ptr puntatore con ownership multipla
– Implementa garbage collector locale
#include <memory>
{
std::shared_ptr<int> ptr1(new int);
std::cout << ptr1.use_count() << std::endl; // 1 std::shared_ptr<int> ptr2(ptr1);
std::cout << ptr1.use_count() << std::endl; // 2 std::cout << ptr2.use_count() << std::endl; // 2 }
Librerie: contenitori associativi
●
Contenitori associativi non ordinati
– Hash tables
– Permettono ricerche in O(1)
– In particolare:
● unordered_set<T>
● unordered_multiset<T>
● unordered_map<K,V>
● unordered_multimap<K, V>
Librerie: contenitori
●
array<T>
– Vettore a dimensione fissa
– Stessa interfaccia di vector (anche come iteratori, e quindi algoritmi chiamabili, ecc)
array<int, 5> a{{1,2,3,4,5}};
int sum = 0;
for_each(a.begin(),a.end(),[&sum](int e){sum+=e;});
Librerie: tuple
●
std::tuple<T1, T2, T3, ...>
– Estendono i std::pair<T1, T2> del C++98
– Contenitore eterogeneo di dimensione fissa // Costruttore
std::tuple<std::string,int,float> t{"ciao", 10, 5.1};
// come make_pair per la tupla. Deduce i tipi in // modo automatico
auto t = std::make_tuple(100, "ciao", 2011,'c');
// Prende un elemento della tupla std::cout << get<2>(t);
Molte altre cose
●
Ottimizzazione: constexpr
●
Template flessibili:
– alias vs typedef, variadic templates, decltype
●
Enum tipati: scoped enum
●
Controllo sull'ereditarieta': final, override
●
Librerie
–
regular expressions, multithreading support
–
Moderne librerie per time e numeri casuali
●
Utilita' generiche: static_assert, raw strings
●