C++11diligmic/PPS2017/Materiale_didattico/12-C++11.pdf · C++11 Nuova revisione dello standard dopo...

30
C++11 Michelangelo Diligenti Ingegneria Informatica e dell'Informazione [email protected]

Transcript of C++11diligmic/PPS2017/Materiale_didattico/12-C++11.pdf · C++11 Nuova revisione dello standard dopo...

C++11

Michelangelo DiligentiIngegneria Informatica e

dell'Informazione

[email protected]

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 sopravector<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 inizializzaticonst 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 interivector<int> v = {3,2,1,5,4};

– mappa con 2 valori inizialimap<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++98vector<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 constructorclass 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● ecc.