• Non ci sono risultati.

Templates e STL

N/A
N/A
Protected

Academic year: 2021

Condividi "Templates e STL"

Copied!
126
0
0

Testo completo

(1)

Templates e STL

Michelangelo Diligenti Ingegneria Informatica e

dell'Informazione

diligmic@dii.unisi.it

(2)

Argomenti

Templates in C++

C++ standard template library (STL)

(3)

Problema con i linguaggi tipati

Funzione per scambiare due variabili int

void Swap(int& first, int& second) { int temp = first;

first = second;

second = temp;

}

Funzione per scambiare due variabili float

void Swap(float& first, float& second) { float temp = first;

first = second;

second = temp;

}

(4)

Problema con i linguaggi tipati

Se poi ho string? E poi due double? ecc.

Serve fare una libreria di funzioni per ogni funzione?

Notate che la logica rimane la stessa

Solo il tipo cambia

Soluzione: fare si che i tipi siano anch'essi parametri

Si definisce una sola funzione

Il tipo diventa un parametro

=> templates

(5)

I templates: vantaggi

Soluzione: fare si che i tipi siano anch'essi parametri => templates

Le funzioni forniscono un template i cui tipi sono specificati di volta in volta

Serve sintassi adeguata per specificare una classe di argomenti diversa

Meccanismo fondamentale per realizzare il code reuse

Tutte le librerie standard del C++ sono realizzate con

questo meccanismo

(6)

Templates in C++: sintassi

Esempio

template <typename Type>

void Swap(Type& first, Type& second) { Type temp = first;

first = second;

second = temp;

}

lista di tipi

funzione che usa

anche i tipi definiti

nel template

(7)

Templates in C++: sintassi

Sintassi:

template<typename Tipo1, …, typename TipoN>

tipo_ritornato funzione(args) { … }

typename indica al compilatore che cosa segue è un tipo anche se per ora sconosciuto

Tipo1, …, TipoN possono essere usati nella

definizione

(8)

Templates in C++: sintassi

Esempio:

#include "date.h"

template <typename Type>

void Swap(Type& first, Type& second) {

const Type tmp = first; first = second; second = tmp; } int main() {

int i1 = 3, i2 = 4;

string s1 = “a”, s2 = “b”;

Date t1, t2;

Swap<int>(i1, i2);

Swap<string>(s1, s2);

Swap<Date>(t1, t2);

}

Lista tipi passata prima di

lista degli argomenti

(9)

Templates in C++: sintassi

Possibile anche omettere la dichiarazione dei tipi desiderati quando si chiama la funzione

compilatore crea la funzione in base ai tipi passati

template <typename Type>

void Swap(Type& first, Type& second) { Type tmp = first;

first = second;

second = tmp;

}

int main() {

int i1 = 3, i2 = 4;

string s1 = “a”, s2 = “b”;

Swap(i1, i2);

Swap(s1, s2);

Swap(i1, s1); // No errore in compilazione!!! Non posso instanziare Type univoco

}

(10)

Templates e compilatore

Templates sono istanziati nel momento in cui il compilatore trova un loro utilizzo

Quando il compilatore trova la definizione non fa nulla tranne qualche controllo di sintassi

Istanziazione nel momento in cui viene chiamata la funzione per tipo

Si costruisce il codice sorgente al volo

(11)

Templates e compilatore

Esempio

template <typename Type>

void Swap(Type& first, Type& second) { Type tmp = first;

first = second;

second = tmp;

}

int main() {

int i1 = 3, i2 = 4;

Swap(i1, i2);

}

Istanzio il template con degli int

Type->int nella funzione

void Swap(int& first, int& second) { int tmp = first;

first = second;

second = tmp;

}

compilatore

genera la funzione e la compila

(12)

Templates e compilatore

L'implementazione deve essere visibile in compilazione e non in linking come di solito

Non basta aver visibile la definizione, compilatore genera il codice al volo con l'implementazione

Il codice generato sostituisce i tipi passati come argomenti nel codice della definizione

Non c'è alcuna penalità in termini di prestazioni

Possibile anche mantenere inlining. Esempio:

template <typename Type>

inline void Swap(Type& first, Type& second) { ...}

(13)

Templates: svantaggi

Svantaggi

Compilazione lenta

Errori del compilatore sono difficilmente comprensibili

File .h (header) più complessi e meno leggibili

(14)

Templates e classi

Template sono un meccanismo ancora più potente

Possibile creare classi che sono templates

Sintassi

template<typename Type1, …, typename TypeN>

class NomeClasse {

// definizione classe come al solito

// i tipi Type1,...,TypeN possono essere riferiti

};

(15)

Templates e classi

Esempio:

template<typename T>

class Vector { public:

Vector(int size) : vec(NULL) { if (size > 0) vec = new T[size]; } inline const T& Get(int i) { return vec[i]; }

inline void Set(int i, const T& el) { vec[i] = el; } ~Vector() { if(vec != NULL) delete[] vec; }

private:

T* vec;

};

metodi inline

(16)

Templates e classi

Se metodi sono complessi meglio non metterli inline

template<typename T>

class Vector { public:

Vector(int size);

inline const T& Get(int i) const { return vec[i]; } inline void Set(int i, const T& el) { vec[i] = el; } ~Vector() { if (vec != NULL) delete[] vec; } private:

T* vec;

};

template<typename T>

Vector<T>::Vector(int size) { if (size <= 0) {

std::cerr << “Wrong parameter”; exit(0);

}

vec = new T[size];

};

metodo non inline

ma sempre insieme

alla definizione nel .h

Deve essere visibile

al compilatore.

(17)

Templates e classi: esempio

Classe che realizza una lista di oggetti template <typename T>

class Stack { private:

class Elem { public:

Elem() { el = NULL; prec = NULL; succ =NULL; } T* el;

Elem* prec;

Elem* succ;

};

Elem* testa;

public:

Stack();

void Push(T* el); // aggiunge elemento

T* Pop(); // ritorna NULL se la lista è vuota ~Stack();

};

classe dentro la classe

Utility class usata solo

nel contesto della classe

esterna

(18)

Templates e classi: esempio

Esercizio:

Implementare la classe pila (Stack) per generici oggetti con tutti i suoi metodi

Implementare un main che chiama la classe, la riempie con 10 oggetti e poi li svuota

ATTENZIONE alla gestione della memoria!

(19)

Templates e classi: esempio

Definizione della classe

template <typename T>

class Stack { private:

class Elem { public:

Elem() { el = NULL; prec = NULL; succ =NULL; } T* el;

Elem* prec;

Elem* succ;

};

Elem* testa;

public:

Stack();

void Push(T* el); // aggiunge elemento T* Pop(); // ritorna NULL se stack vuoto ~Stack();

};

(20)

Templates e classi: esempio

Metodo Push (in .h)

template <typename T>

void Stack<T>::Push(T* el) { Elem* elem = new Elem;

elem->el = el;

elem->prec = testa;

if (testa != NULL)

testa->succ = elem;

testa = elem;

}

(21)

Templates e classi: esempio

Metodo Pop e costruttore (in .h)

template <typename T>

T* Stack<T>::Pop() {

if (testa == NULL) return NULL;

T* el = testa->el;

Elem* prec = testa->prec;

delete testa;

testa = prec;

if (testa != NULL) testa->succ = NULL;

return el;

}

template <typename T>

Stack<T>::Stack() { testa = NULL; }

(22)

Templates e classi: esempio

Distruttore deve essere aggiunto

si alloca la memoria dinamicamente

template <typename T>

Stack<T>::~Stack() {

while(Pop() != NULL) {}

}

(23)

Templates e classi: esempio

Main

#include <iostream>

#include “stack.h”

int main() {

Stack<int> pila;

for (int i = 0; i < 10; ++i) {

std::cout << "Add " << i << std::endl;

pila.Push(new int(i));

}

int* res = NULL;

while ((res = pila.Pop()) != NULL) {

std::cout << "Pop " << *res << std::endl;

delete res;

}

return 0;

}

Instanzio il template T=int

Crea una pila di int ma poteva

essere qualsiasi altro tipo

(24)

Ownership dei dati

Usare T* in Elem implica che la gestione della memoria dei dati e' esterna alla classe

Si dice che la classe non ha “ownership” dei dati

Vantaggio

Evita le copie in Push e Pop

Istanze di T possono anche non essere copiabili

Se copy constructor o operator= non sono definiti per T il template resta utilizzabile

Svantaggio

Allocazione e deallocazione viene gestita esternamente

Facile sbagliare per chi usa la classe

(25)

Ownership e soluzione 2

Soluzione alternativa, dati sono proprieta' (“owned”) dalla classe

template <typename T>

class Stack { private:

class Elem {

T el; // costruttore T() invocato in costruzione // come prima

...

};

void Push(const T& el_);

T Pop();

// Ora non posso fare check che el sia NULL inline bool Empty() const { testa != NULL; } ...

};

(26)

Ownership e soluzione 2

template <typename T>

void Stack<T>::Push(const T& el) { … /* come prima */

elem->el = el; // invocazione all'operator=

}

template <typename T>

T Stack<T>::Pop() {

const T& el = testa->el;

… /* come prima */

return el; // invocazione al copy constructor }

int main() {

Stack<int> lista;

for (int i = 0; i < 10; ++i) lista.Push(i);

while (!lista.Empty())

std::cout << "Pop " << lista.Pop() << std::endl;

return 0;

}

(27)

Classi e Ownership

Esempio: classi Vector e Matrix viste in precedenza avevano ownership interna alla classe

Attenzione: le librerie standard usano ownership interna

Dati sono copiati in inserimento

Allocazione e deallocazione viene gestita

direttamente nella classe

(28)

Classi e Ownership

Templates mi permettono un trucco

Avere ownership sia interna che esterna!

template <typename T>

class Stack { private:

class Elem { … }; // come prima

void Push(const T& el_); // per valore T Pop(); // per valore

...

};

Stack<int> s1;

for (int I = 0; I < 10; +I) s1.Push(i);

Stack<int*> s2;

for (int i = 0; i < 10; +i) s2.Push(new int(i));

… // deallocare I dati di s2!

Conviene definire inserimenti e ritorno per valore

Se instanzio il template con il dato ownership dei dati diviene interna

Se instanzio il template con

puntatore: ownership dei puntatori interna ma i dati puntati hanno

ownership esterna

(29)

Templates e compilatore

Sorgente generato deve essere corretto

Non tutti i tipi sono compatibili con un template

template<typename T>

class Numero { private:

T num;

public:

Numero(const T& n) : num(n) { } void Add(const T& n) {

num = num + n;

} };

// Nel main chiamo Numero<int> num(3);

Istanzio con int.

Codice generato:

class Numero { private:

int num;

public:

Numero(const int& n) : num(n) { } void Add(const int& n) {

num = num + n;

} };

(30)

Templates e compilatore

Sorgente generato deve essere corretto

Non tutti i tipi sono compatibili con un template

template<typename T>

class Numero { private:

T num;

public:

Numero(const T& n) : num(n) { } void Add(const T& n) {

num = num + n;

} };

// Nel main chiamo

Numero<Date> num(Date(3,3,15));

Istanzio con Date: operator+

deve essere definito tra date) o ERRORE in compilazione:

class Numero { private:

Date num;

public:

Numero(const Date& n) : num(n) { } void Add(const Date& n) {

num = num + n;

} };

(31)

Templates e valori di default

Possibile specificare un tipo di default per un template

Simile a valori di default per funzioni

Sintassi

template<typename T=TipoDefault> class NomeClasse { … }

Esempio

template <typename T=int> class Vector { … }

Se scrivo =>

Vector vec; // nulla specificato sul tipo, sarà vettore di interi

(32)

Templates ed ereditarietà

Possibile usare ereditarietà in classi con templates

Templates instanziano codice che può contenere classi che contengono ereditarietà

Usato spesso in librerie standard

Esempio realizzare una libreria che crea contenitore pila ed una coda generici (con template)

Entrambe si basano su una lista: creo lista con template

Uso ereditarietà per definire una coda ed una pila

(33)

Templates ed ereditarietà

Classe lista generica

Costruttore tenuto protected, non deve essere usata se non dalle classi derivate

template<typename T>

class List { private:

class Elem { … Come prima … };

Elem* testa;

Elem* coda;

protected:

List() : testa(NULL), coda(NULL) { } void PushCoda(T* el) { … }

void PushTesta(T* el) { … } T* PopCoda() { … }

T* PopTesta() { ... }

};

(34)

Templates ed ereditarietà

template<typename T>

class Stack : public List<T> { public:

Stack() { }

void Push(T* el) {

this->PushTesta(el); } T* Pop() {

return this->PopTesta(); };

};

template<typename T>

class Queue : public List<T> { public:

Queue() { }

void Enqueue(T* el) { this->PushCoda(el); } T* Dequeue() {

return this->PopTesta(); } };

Template con ereditarietà

Si richiama solo i metodi di lista, nessuna

implementazione

aggiuntiva

(35)

Templates e composizione

Composizione con templates: dati membri possono a loro volta essere istanze di classe templetizzata

Esempio realizzare una libreria che crea contenitore pila ed una coda generici (con template)

Entrambe si basano su una lista: creo lista con template

Uso composizione per definire la lista come dato membro in coda e pila

Metodi di classi composte chiamano i metodi di lista

(36)

Templates e composizione

template<typename T>

class Stack { private:

List<T> list;

public:

void Push(T* el) { list.PushTesta(el); } T* Pop() {

return list.PopTesta(); };

};template<typename T>

class Queue { private:

List<T> list;

public:

void Enqueue(T* el) { list.PushCoda(el); } T* Dequeue() {

return list.PopTesta(); } };

Template con composizione (migliore scelta in questo Caso) rispetto a ereditarieta' Dato membro ha

instanzazione rispetto a T

Chiamo metodi su dato

membro

(37)

Standard Template Library

Dal 1994 fa parte dello standard del C++

Collezione di librerie di contenitori come vettori, dizionari, ecc

Permette di scrivere codice in modo veloce, affidabile, compatto ed elegante

Grande generalità

Poiché standard è molto portabile su tutte le piattaforme

Architettura software

a classi

basata su templates per gestire qualsiasi tipo e classe

personalizzabile tramite definendo operatori appropriati

(38)

Standard Template Library

Contiene la classe string

Ed i seguenti contenitori con un insieme di algoritmi associati

Dentro il namespace std (standard)

Sequenziali: deque, list, vector

Associativi: map, multimap, multiset, set Adattatori: priority_queue, queue, stack

ATTENZIONE: Fondamentale imparare ad usarla

(39)

Vector: dichiarazione

Vettore di oggetti

template<typename T> class std::vector {}

Per usarlo dichiarare

#include <vector>

ed opzionalmente “using namespace std;”

Costruttori

vector<T> vec; // costruisce vettore vuoto vector<T> vec(100); // 100 elementi uguali a T() T t; vector<T> vec(100, t); // 100 elementi uguali a t vector<T> vec1(vec); // copy costructor

(40)

Vector: metodi

Basato su concetto di size e capacity

size è la dimensione del vettore in elementi inseriti

capacity è dimensione della memoria allocata

v.capacity(), v.size() // getters a capacity e size

v.reserve(n) // capacity=n, rialloca la memoria v.push_back(T) // aggiunge un elemento in fondo

// (size++ ed eventualmente rialloca se capacity<size) v.pop_back() // elimina ultimo elemento, size--

v[i] o v.at(i) // ritorna elemento i-esimo

v.begin() // ritorna iteratore (vedremo cosa sia) al primo elemento v.end() // ritorna iteratore subito dopo ultimo elemento

v.front() // ritorna iteratore al primo elmento v.back() // ritorna iteratore all'ultimo elmento v.clear() // svuota il vettore

(41)

Vector: esempio

std::vector <int> v;

v.push_back(10);

v.push_back(3);

v.push_back(6);

for (int i = 0; i < v.size(); i++) std::cout << v[i] << std::endl;

Se non si deve ottimizzare l'uso del vettore si basa solo su push_back per aggiungere e v[i] per leggere il vettore

Allocazione memoria fatta dalla classe in modo

automatico

(42)

Vector: esempio

std::vector <int> v;

v.reserve(3);

v.push_back(10);

v.push_back(3);

v.push_back(6);

for (int i = 0; i < v.size(); i++) std::cout << v[i] << std::endl;

Potenzialmente più veloce quando il vettore cresce molto. Usabile solo se la dimensione è nota a priori

So che la dimensione sarà 3,

per cui dico subito alla classe di

allocare 3 elementi.

(43)

Vector: iteratori

Iterare su vettori si fa semplicemente con ciclo for

Ma il concetto di iteratore sarà fondamentale per altri contenitori per cui vediamolo da subito

vale per tutti i contenitori stl

delega ad una classe iteratore il compito di scorrere gli elementi

l'iteratore generalizza il concetto di puntatore

ad ogni passo l'iteratore ritorna il puntatore ad un elemento

quando lo si incrementa il puntatore passa

all'elemento successivo

(44)

Vector: iteratori

Esempio

std::vector<int> v;

v.push_back(1);

v.push_back(2);

std::vector<int>::iterator iter = v.begin();

for (; iter != v.end(); iter++)

std::cout << *iter << std::endl;

iteratore che punta a primo elemento

vai all'elemento successivo

finche' non si punta alla fine

(45)

Vector: vantaggi

In C

int vec[N]; // dimensione fissa

oppure int* vec = (int*) malloc(N*sizeof(int));

e poi realloc(vec, N1*sizeof(int)); // per riallocare

In C++

vector<int> vec(N);

vec.reserve(N1); // per riallocare

VANTAGGI

Chiaro come sono inizializzati gli elementi (usando il default costructor)

Gestione della memoria trasparente (allocazione fatta dal costruttore, deallocazione fatta dal distruttore del vettore)

Permette la crescita dinamica, senza dover gestire la memoria

(46)

Vector: vantaggi

Efficienza pari a quella dei il vettore del C

Accesso ad un elemento O(1)

Riallocazione costo O(n) quando avviene

Si deve riallocare la memoria e ricopiare l'intero vettore nella nuova locazione

ATTENZIONE: in realtà si riesce a fare poche

riallocazioni allocando un blocco di elementi in più e non solo uno alla volta

ci pensa il push_back ad ottimizzare

Conclusione: molti vantaggi, nessuna penalita'

(47)

Containers e copy constructor

Qualsiasi oggetto può essere usato in un container STL?

Per avere un funzionamento corretto, oggetto deve avere un default constructor ed essere copiabile

ATTENZIONE: copy constructor deve funzionare!

Se default non va bene deve essere definito

Quando si inserisce in contenitore, dato è

copiato: byte-copy o chiamata a copy constructor se definito

ATTENZIONE se l'oggetto memorizzato è grande

meglio memorizzare i puntatori

(48)

Containers e copy constructor

#include <stdio.h>

#include <sys/time.h>

#include <vector>

#include <iostream>

int main() {

struct timeval starttime; gettimeofday(&starttime, NULL);

std::vector<std::vector<int> > matrix;

for (int i = 0; i < 100; ++i) {

std::vector<int> vec(1000000, 1);

matrix.push_back(vec);

}

struct timeval endtime; gettimeofday(&endtime, NULL);

long msec = 1000000 * (endtime.tv_sec - starttime.tv_sec) + (endtime.tv_usec - starttime.tv_usec);

std::cout << msec << “\n”;

}

push_back copia l'intero vettore

(49)

Containers e copy constructor

#include <iostream>

#include <sys/time.h>

#include <vector>

int main() {

struct timeval starttime; gettimeofday(&starttime, NULL);

std::vector<std::vector<int>* > matrix; // vettore di puntatori a vettori for (int i = 0; i < 100; ++i) {

std::vector<int>* vec = new std::vector<int>(1000000, 1);

matrix.push_back(vec);

}

struct timeval endtime; gettimeofday(&endtime, NULL);

long msec = 1000000*(endtime.tv_sec - starttime.tv_sec) + (endtime.tv_usec - starttime.tv_usec);

std::cout << msec << “\n”;

for (int i = 0; i < 100; ++i) delete matrix[i];

}

deallocazione

push_back copia solo il puntatore

(50)

Memorizzare puntatori?

TUTTI i contenitori STL copiano all'inserimento

Come evitare la copia

Memorizzando i puntatori agli oggetti

std::vector<Date*> invece di std::vector<Date>

Quando usare i puntatori come dati da memorizzare

Se la copia è inefficiente

Se non abbiamo implementato un copy constructor ed il byte copy non funziona correttamente

Se gli oggetti da memorizzare sono astratti

Se si vuole ritardare l'inizializzazione degli oggetti

Se si usano i puntatori ricordarsi di deallocare!

(51)

Esercizio

Creare una gerarchia di classi per gestire le persone che lavorano o studiano all'università di Siena

Persone sono o studenti o ricercatori o docenti o ammistratori

Tutti hanno nome, luogo di nascita, indirizzo di lavoro

Studenti hanno inoltre una facoltà e corso di laurea che frequentano

Ricercatori hanno degli argomenti di ricerca

Docenti hanno dei corsi che insegnano

Amministrativi hanno un ufficio di appartenenza

Deve essere possibile salvare o rileggere ogni persona su/da una stringa che ne codifica le sue caratteristiche

Definire una classe che definisce un Dipartimento come un insieme di Persone (usa std::vector)

Deve essere possibile stampare su file la stringa

descrittiva di tutte le persone del Dipartimento

(52)

Deque

Simile ad un vettore ma non rialloca mai l'intera memoria quando si aumenta la capacità

Un elemento sta nella stessa cella di memoria per tutta la sua vita nella deque

Non ha equivalenza con nessun costrutto C: anche se è, come qualsiasi cosa, implementabile in C

Chiave allocazione a blocchi

Quando si esaurisce lo spazio si rialloca un nuovo blocco

Implementata in stl da contenitore

template<typename T> class std::deque { }

Utilizzabile mediante #include<deque>

(53)

Deque

Accesso resta O(1) ma con due dereferenziazioni

Prima si cerca il blocco

e poi dentro il blocco

vector

deque

puntatori ai blocchi

detta array_map

(54)

Deque e complessità

Accesso deque[i]

Prima si cerca il blocco

num_blocco = i / num_elementi_per_blocco

Trovato il blocco vado nell'array_map[num_blocco e trovo il puntatore al blocco: p_blocco

Poi dentro il blocco accedo all'emento con

p_blocco[i % num_elementi_per_blocco]

Più lento del vettore di una costante ma sempre O(1)

Aggiunta di elementi con push_back

Se l'ultimo blocco è esaurito si deve

allocare blocco con costo fisso O(num_elementi_per_blocco)

costo è costante ed è O(1) rispetto alla size della deque

(55)

Deque: metodi

Molto simile al vettore (sono “quasi” itercambiabili) a parte i metodi relativi alla capacità

d.size() // ritorna size

d.push_back(T) // aggiunge un elemento in fondo (size++) d.pop_back() // elimina ultimo elemento, size--

d[i] o d.at(i) // ritorna elemento i-esimo.

d.begin() // ritorna iteratore al primo elemento

d.end() // ritorna iteratore subito dopo ultimo elemento d.front(), d.back() // ritorna puntatore a primo o ultimo elmento d.clear() // svuota la deque

(56)

Deque: esempio

#include<deque>

std::deque <int> d;

d.push_back(1);

d.push_back(2);

for (int i = 0; i < d.size(); i++) std::cout << d[i] << std::endl;

// o con gli iteratori

std::deque<int>::iterator iter = d.begin();

for (; iter != d.end(); iter++)

std::cout << *iter << std::endl;

(57)

List

Lista ha complessità lineare per le rimozioni od inserimenti in qualsiasi punto

Vettori e deque veloci per accessi random o sequenziali

Inserimento e rimozione di elementi non in coda è lento O(size)

Non abbiamo visto come farlo e solo mostrato push_back per aggiungere in coda...

(58)

List

Implementata in stl da contenitore

template<typename T> class std::list { }

Utilizzabile mediante

#include<list>

Implementa una lista come la conoscete voi

VANTAGGI

Contenitore generico di tipi grazie ai template

Non dovete nemmeno pensare all'implementazione che usa puntatori ecc.

Sicura e veloce

(59)

List: metodi

l.size() // ritorna size

l.push_back(T) // aggiunge un elemento in fondo (size++) l.push_front(T) // aggiunge un elemento in cima (size++) l.pop_back() // elimina ultimo elemento, size-- l.pop_front() // elimina primo elemento, size--

l.begin() // ritorna iteratore al primo elemento

l.end() // ritorna iteratore dopo ultimo elemento

l.front() // ritorna puntatore a primo elemento

l.back() // ritorna puntatore a ultimo elemento

l.clear() // svuota la lista

(60)

List: esempio

#include<list>

std::list <int> l;

l.push_back(1);

l.push_front(2);

l.push_back(4);

// non ammette accesso random, si deve usare gli iteratori std::list<int>::iterator iter = l.begin();

for (; iter != l.end(); iter++) std::cout << *iter << std::endl;

(61)

Stl e adattatori

Partendo dall'implementazione di contenitori base, STL realizza altri contenitori

Sotto si usa una lista o deque o vettore

Ma l'interfaccia è rivista per rendere facilmente accessibili solo alcune funzionalità

Realizzano quello che si chiama un Wrapper

Incapsulano la classe rendendo accessibili

alcune cose e nascondendone altre

(62)

Stack

Realizza uno stack (code FIFO)

Implementata a partire da una deque

Definita come

template<typename T> class stack { }

Per usarla aggiungere

#include<stack>

Metodi

push(el) // aggiunge un elemento (size++)

pop() // rimuove un elemento (size--)

top() // prende l'elemento in testa

size() // dimensione dello stack

empty() // bool che controlla se vuota

(63)

Stack: esempio

#include <iostream>

#include <stack>

#include <string>

using namespace std;

int main() {

stack<string> allwords; // stack of di parole string word;

while (cin >> word) // legge le parole da stdin allwords.push(word);

while (allwords.size() > 0) {

cout << allwords.top() << endl; // scrive la parola su stdout allwords.pop(); // rimuove la parola

}

return 0;

}

(64)

Coda con priorità

Realizza una coda con priorità

Elemento massimo (o minimo) sempre in testa alla coda

Inserimento e rimozione O(log(n))

Implementa struttura dati heap

Definita come

template<typename T> class priority_queue { }

Per usarla aggiungere

#include<queue>

Possibile cambiare funzione di ordinamento

(65)

Coda con priorità: metodi

push(el) // aggiunge un elemento (size++)

pop() // rimuove un elemento (size--)

top() // prende l'elemento in testa

size() // dimensione dello stack

empty() // ritorna true se vuota

(66)

Coda con priorità: heap

Implementazione della coda con priorità

Usa struttura dati detta Heap

Memorizzazione dell'albero attraverso un vettore,

=> le code con priorità sono adattatori nell'stl

Heap è un albero binario che ha le seguenti proprietà (heap property)

La radice memorizza il valore minimo tra tutti i nodi dell'albero

Valore memorizzato in un nodo è sempre

superiore al valore memorizzato nel padre

(67)

Heap

Esempio albero dove la heap property è verificata

Potrei anche usarlo con ordinamento crescente

9 8

6

2

4 7

3

(68)

Heap: aggiunta nodi

Inserimento elemento

Aggiunta in prima posizione libera

Riaggiorno l'albero, scambiando i nodi finché proprietà heap non è verificata

Complessità O(log n)

9 8

6

2

Albero iniziale 3

(69)

Heap: aggiunta nodi

Inserimento elemento

Aggiunta in prima posizione libera

Riaggiorno l'albero, scambiando i nodi finché proprietà heap non è verificata

Complessità O(log n)

9 8

6

2

3 Aggiungo elemento

in prima posizione libera Heap property non più verificata

1

(70)

Heap: aggiunta nodi

Inserimento elemento

Aggiunta in prima posizione libera

Riaggiorno l'albero, scambiando i nodi finché proprietà heap non è verificata

Complessità O(log n)

9 8

6

2

1 Scambio nodi fino a che heap property non è di nuovo verificata

3

(71)

Heap: aggiunta nodi

Inserimento elemento

Aggiunta in prima posizione libera

Riaggiornamento albero, scambiando i nodi finché proprietà heap non è verificata

Complessità O(log n)

9 8

6

1

2 Scambio nodi fino a che

heap property non è di nuovo

verificata. Max log(n) passi

3

(72)

Heap: rimozione nodi

Rimozione elemento nella radice

Eliminazione radice, sostituita da ultimo nodo nell'albero

Riaggiornamento l'albero, scambiando i nodi finché proprietà heap non è verificata

Complessità O(log n)

9 8

6

1

2 Eliminazione nodo

3

(73)

Heap: rimozione nodi

Rimozione elemento nella radice

Eliminazione radice, sostituita da ultimo nodo nell'albero

Riaggiornamento l'albero, scambiando i nodi finché proprietà heap non è verificata

Complessità O(log n)

9 8

6 2 Sostituzione con ultimo

nodo dell'albero

3

(74)

Heap: rimozione nodi

Rimozione elemento nella radice

Eliminazione radice, sostituita da ultimo nodo nell'albero

RIaggiornamento l'albero, scambiando i nodi finché proprietà heap non è verificata

Complessità O(log n)

9 8

6 2 Sostituzione con ultimo

nodo dell'albero

Heap property non verificata

3

(75)

Heap: rimozione nodi

Rimozione elemento nella radice

Eliminazione radice, sostituita da ultimo nodo nell'albero

RIaggiornamento l'albero, scambiando i nodi finché proprietà heap non è verificata

Complessità O(log n)

9 8

6 3 Scambio nodi finché

heap property

non viene verificata, in max log(n) passi

2

(76)

Coda con priorità: heap

Push e pop hanno complessità O(log(n)), n dimensione dei dati

Top ha complessità O(1)

semplice accesso alla radice

Inserire un insieme di dati e rileggerlo elemento ad elemento ordinato complessità O(n log(n))

Non deve stupire visto che nlog(n) è ottimo

ottenibile per l'ordinamento di un insieme!

(77)

Coda con priorità: esempio

#include <iostream>

#include <queue>

using namespace std;

int main () {

priority_queue<int> mypq;

mypq.push(30);

mypq.push(100);

mypq.push(25);

mypq.push(40);

while (!mypq.empty()) {

cout << mypq.top() << " ";

mypq.pop();

}

cout << endl;

return 0;

}

(78)

Code: ordinamento custom

La coda con priorità è utilissima ma se mi serve ordinamento speciale?

In generale come definisco ordinamento per oggetti generici nell'STL?

Quello che vediamo adesso usabile in tutti i

contenitori ordinati dell'STL (priority_queue, map, etc.)

Nota:

Ordinamento sempre realizzato tramite confronti tra coppie di oggetti

Definire odinamento tra coppie definisce

ordinamento totale

(79)

Code: ordinamento custom

Metodo standard

definisco una class/struct che fa da comparatore

Implementa operator() tra due istanze

Esempio per coppie di istanze di date:

struct CompareDate {

bool operator()(const Date& a, const Date& b) const { return a.GetYear() < b.GetYear();

} };

Vantaggio:

Diverse classi comparatore a seconda delle esigenze

Esempio per fare ordinamento decrescente return b.GetYear() < a.GetYear();

(80)

Code: ordinamento custom

Classe comparatore:

#include “date.h”

struct CompareDate {

bool operator()(const Date& a, const Date& b) const { return a.GetYear() < b.GetYear();

} };

// Per definire un'istanza

priority_queue<Date, vector<Date>, CompareDate> coda;

Classe che mi determina ordinamento:

terzo parametro del template

Secondo parametro del template è il contenitore usato, in genere non va cambiato il default: vector<...>

Necessario ridefinirlo anche se usate il default perché cambiate il terzo parametro

Dichiarazione di

classe comparatore

tra Date

(81)

Code: ordinamento custom

Metodo di default:

Se non definite il comparatore e scrivete

priority_queue<Date> coda;

Compilatore utilizza valori di default dei parametri del template priority_queue<Date, vector<Date>, less<Date> > coda;

less<T> classe templetizzata definita nelle librerie template<typename T>

struct less {

bool operator()(const T& l, const T& r) const { return l < r;

}

};

Less invoca operator<

sulle classi

Comparatore di default

(82)

Code: ordinamento custom

Ordinamento di default senza comparatore:

priority_queue<Date> coda;

Comparatore di default less<T> confronta due istanze oggetti a,b con il test:

a < b

Viene invocato l'operator< tra coppie di istanze

Attraverso operator overloading di operator<, diviene realizzabile ordinamento custom

Esempio: priority_queue<Date> coda;

OK solo se e' definito operator<(const Date& date1, const Date& date2)

Altrimenti errore in compilazione appena viene istanziato il template

(83)

Code: ordinamento custom, esercizio

Esercizio: implementare una classe Date, con overloading operator< che confronta due date

Realizzare due code una che ordina le date in modo descrescente ed una crescente

Scrivere un main che riempie le code (usando le librerie STL) con delle date e poi le svuota

stampando i valori inseriti

(84)

Iteratori e contenitori

Il mondo degli iteratori STL è variegato

Iteratori standard quelli visti fin qui

Iteratori inversi (reverse iterator) per scandire il contenitore all'indietro

Iteratori const, che non permettono di modificare gli oggetti scanditi

Obbligatori se usati in metodi const o per container dichiarati const

E molti altri... ma con i tre suddetti si coprono le

esigenze al 99.9%

(85)

Iteratori e contenitori const

#include<list>

std::list<int> l;

l.push_back(1);

l.push_front(2);

std::list<int>::iterator iter = l.begin();

for (; iter != l.end(); iter++)

(*iter)++; // modifico il contenuto

std::list<int>::const_iterator iter = l.begin();

for (; iter != l.end(); iter++)

std::cout << *iter << “\n”; // OK non modifico il contenuto std::list<int>::const_iterator iter = l.begin();

for (; iter != l.end(); iter++)

(*iter)++; // ERRORE in compilazione modifico il contenuto

Iteratori possono

modificare il contenitore

Iteratori const non possono

modificare il contenuto

(86)

Iteratori e contenitori inversi

#include<list>

std::list<int> l;

l.push_back(1);

l.push_front(2);

std::list<int>::reverse_iterator iter = l.rbegin();

for (; iter != l.rend(); iter++) (*iter)++;

std::list<int>::reverse_const_iterator iter = l.rbegin();

for (; iter != l.rend(); iter++)

std::cout << *iter << std::endl;

scandisco all'indietro

e possibilmente modifico i dati

scandisco all'indietro

ma non posso modificare

i dati

(87)

Contenitori associativi

Permettono di associare oggetti ad oggetti

chiave → valore

O di creare insieme di oggetti

Vantaggi

Permettono la ricerca efficiente

Tipicamante O(log(n))

Per esempio possibile creare dizionari

parola → numero_occorrenze

(88)

Map

Implementata in stl da contenitore

template<typename T1, typename T2> class std::map { }

Utilizzabile mediante

#include<map>

Implementa un albero binario di ricerca ordinato per chiave

VANTAGGI

Contenitore generico di tipi grazie ai template

Ricerche per chiave hanno complessità O(log(n))

Inserimenti hanno complessità O(log(n))

(89)

Map: metodi

m.size() // ritorna dimensione della mappa m.insert(make_pair(T1, T2)) // aggiunge coppia chiave->valore

m[T1] = T2 // operatore equivalente a m.insert(make_pair(T1, T2)) m.begin() // ritorna iteratore a prima coppia (chiave,valore)

m.end() // ritorna iteratore subito dopo ultima coppia m.clear() // svuota la mappa

m.erase(T1) // rimuove un elemento

m.find(T1) // ritorna iteratore a (T1,valore) o m.end() se non trovato Ritorna una coppia chiave → valore

Realizzata tramite la classe pair:

template <typename T1, typename T2> class pair { T1 first;

T2 second;

pair(const T1& f, const T2& s) : first(f), second(s) {}

};

(90)

Map: operator[]

operator[] ritorna riferimento ad elemento associato

Esempi

map<string, int> m;

m[“pippo”] ritorna int per cui possibile m[“pippo”]++; m[“pippo”] = 0;

oppure

map<int, string> m1;

m[4] = “ciao”; m[4].find(“d”);

(91)

Map: operator[]

operator[] non e' const

Se elemento non presente viene inserito

Inizializzazione tramite costruttore di default

Esempi

map<string, int> m; m[“pippo”]++;

m[“pippo”] aggiunge elemento pari a 0 chiama costruttore di default di int: int() poi lo incrementa, valore finale e' 1

map<int, string> m1; m[5] = “ciao”;

m[5] crea stringa vuota chiamando string()

poi si assegna a “ciao”

(92)

Map: creazione di bag of words

#include <iostream>

#include <map>

#include <string>

using namespace std;

int main() {

map<string, int> freq; // dizionario parole → frequenza string word;

while (cin >> word) { // leggo parole dall'input

freq[word]++; // oppure freq.insert(make_pair(word, 0))->second++;

}

map<string, int>::const_iterator iter;

for (iter=freq.begin(); iter != freq.end(); ++iter) {

cout << iter->first << "->" << iter->second << endl;

}

iter = freq.find(“pippo”);

if (iter != freq.end())

cout << “Pippo ” << iter->second;

return 0;

}

iteratore ritorna pair:

iter->first chiave

iter->second: valore Ricerche su mappa

tramite find

(93)

Map: esercizi con mappe STL

1) Leggere un testo in ingresso da file, creare bag of words, stampare le parole

2) Un docente deve gestire i voti assegnati ai

suoi studenti. Creare una classe per la gestione:

leggere da file l'insieme di nomi delle persone che frequentano un corso e voto associato,

permettere la ricerca del voto dato il nome di uno studente.

Suggerimento: creare una classe che contenga una mappa nome->voto e costruttore che

prende il nome del file da cui caricare i dati ed

un metodo per la ricerca.

(94)

Multimap

Map ha un solo elemento data una chiave

Multimap ammette più elementi con stessa chiave

typename<K, V> std::multimap<K, V>

Stessa interfaccia di std::map ma:

senza operator[], necessario usare m.insert()

senza metodo find, vedi prossima slide

Attenzione: possibile ottenere una cosa simile a multimap con

std::map<K, std::vector<V> >

(95)

Multimap

Ricerca su multimap

multimap<int, string> m2;

pair<multimap<int, string>::iterator,

multimap<int, string>::iterator> range;

range = m2.equal_range(10);

multimap<int, string>::iterator iter;

for (iter = range.first; iter != range.second; ++iter)

cout << iter->first << ” “ << iter->second << “\n”;

(96)

Map: esercizi con multimap

1) Un docente deve gestire i voti assegnati ai suoi studenti. Creare una classe per la gestione dei voti. Leggere da file l'insieme di nomi delle persone che frequentano un corso e voto

associato, stampare gli studenti in ordine di voto.

Suggerimento: riprendere da esercizio

precedente ed aggiungere un metodo dove: si copia la mappa in una multimap con chiave e

valore invertiti e stampare con un'iterazione sulla

multimap.

(97)

Mappe ed ordinamento custom

La mappa contiene un terzo elemento nel template

map<T1, T2, Comparator>

Compare è la funzione di ordinamento per confrontare le coppie

Default chiama una funzione di comparazione che chiama operator< per ordinare

Possibile definire un'altra funzione di ordinamento

(98)

Mappe ed ordinamento custom

Esempio, ordinare in modo descrescente

struct cmp {

bool operator() (const int& a, const int& b) { return b < a;

} };

map<int, string, cmp> mappa;

mappa[3] = “ciao”;

mappa[5] = “ciacai”;

mappa[2] = “ciaociao”;

map<int, string, cmp>::const_iterator iter = mappa.begin();

for (; iter != mappa.end(); ++iter)

std::cout << iter->first << “->” << iter->second << “\n”;

Dichiarazione di classe comparatore

Con b < a, ordino in modo decrescente, a < b, in modo crescente (default)

(99)

Set

Implementata in stl da contenitore

template<typename T> class std::set { }

Utilizzabile mediante

#include<set>

Implementa un insieme di oggetti

Organizzati in un albero binario di ricerca ordinato per chiave

VANTAGGI

Contenitore generico di tipi grazie ai template

Ricerche per chiave hanno complessità O(log(n))

Inserimenti hanno complessità O(log(n))

(100)

Set: metodi

s.size() // ritorna size

s.insert(T) // aggiunge un elemento

s.begin() // ritorna iteratore al primo elemento

s.end() // ritorna iteratore subito dopo ultimo elemento s.clear() // svuota la mappa

s.erase(T) // rimuove un elemento

s.find(T) // ritorna iteratore a elemento o s.end() se non trovato

(101)

Mappe/Set: ordinamento di classi

Abbiamo visto map<K, V, Comparator>

Comparator di default chiama operator< attraverso l'uso di Comparator=less<K>

Per usare una classe generica come chiave in una mappa è necessario che l'operator< sia definito

Per tipi noti (int, float, ecc) il compilatore sa come confrontarli e non si deve fare nulla

Oppure possibile definire una funzione di

comparazione custom come abbiamo visto

(102)

Mappe ed ordinamento di classi

Esempio, ordinare date senza usare operator<

(fate voi il caso in cui si usa operator<)

struct cmp {

bool operator() (const Date& a, const Date& b) {

if (a.GetYear() != b.GetYear()) return a.GetYear() > b.GetYear();

if (a.GetMonth() != b.GetMonth()) return a.GetMonth() > b.GetMonth();

return a.GetDay() > b.GetDay();

} };

map<Date, int, cmp> mappa;

mappa[Date(1,1,2011)] = 1;

mappa[Date(1,1,2010)] = 3;

mappa[Date(3,3,2012)] = 5;

map<Date, int, cmp>::const_iterator iter = mappa.begin();

for (; iter != mappa.end(); ++iter) {

std::cout << iter->first.GetYear() << iter->first.GetMonth()

<< iter->first.GetDay() << “->” << iter->second << “\n”;

}

(103)

Map: esercizi

Esercizio 1

Leggere un testo in ingresso da file

Creare bag of words

Stampare le parole in ordine decrescente di frequenza (consiglio usare multimap e

comparatore)

(104)

Map: esercizi

Esercizio 2:

una compagnia aerea tiene la lista delle persone che viaggeranno su un singolo volo su un file

Creare una classe che permetta di leggere il file, memorizzando i nomi delle persone

Permettere alla polizia di cercare se una

persona viaggia su un certo volo, o stampare

tutte le persone viaggianti in ordine alfabetico

(105)

Multiset

Multiset

Set che può avere piu' volte lo stesso elemento inserito

Definito come

template<typename T> class std::set { }

Utilizzabile mediante

#include<set>

(106)

Tabelle hash

unsortedmap (compilatori vecchi hash_map)

Interfaccia come map, ma si definisce funzione hash invece che operatore di ordinamento per tipi custom

Accesso ad un elemento ha costo indipendente dalla dimensione della tabella O(1)

recente aggiunta allo standard (c++-11)

unsortedset (compilatori vecchi hash_set)

Test di presenza nel set O(1)

Da standard c++-11

Basate su tabelle hash, anche se lo standard non si

lega ad una particolare implementazione

(107)

Tabelle hash: implementazione

Hash function mappa la chiave su indice numerico

Indice numerico accede ad elemento in vettore

Vettore memorizza le coppie chiave-valore

Hash Function

Hash

Chiavi Lista coppie chiave-valore Indice 152 ha conflitto. La lista chiavi valore permette di capire, se il match per una chiave e' corretto e prendere il valore corretto tra quelli nella lista

(108)

Unsortedmap / Unsortedset

Definite come

template<typename Key,typename Value, typename Hash = hash<Key> >

unsortedmap

template<typename Key, typename Hash = hash<Key> > unsortedset

Funzioni di Hash sono passate tramite template

hash<string>, hash<float>, etc. gia' definite da stl

Farle per classi custom non e' semplice, dobbiamo garantire l'uniformita del mapping sull'intervallo

Interfaccia di fatto identica a map/set

Unsortedmap: operator[], clear, insert, erase,

find, begin, end, size, etc.

Riferimenti

Documenti correlati

• quando, in seguito a un inserimento, si ha α = 1 (cioè ogni lista contiene in media 1 elemento), si crea una nuova tabella di dimensione 2M (cioè doppia), vi si trasferiscono i

Dimostriamo che esiste sempre una soluzione ottima che contiene la scelta ingorda, ovvero in cui il job con durata pi`u corta sia scelto per primo.. Supponiamo di avere una

• se il numero di round è pari a 2, qualsiasi rete di Feistel non può essere considerata una permutazione pseudocasuale:. • ogni rete di Feistel con numero di round pari a 3 non è

 For each hash table entry, a list of elements stores all data items having the same hash function value. 

Tenuto conto, nelle more del perfezionamento dell'adesione alla Convenzione Consip FM 3 che inciude anche il servizio di facchinaggio interno ed esterno e che

[r]

 Dalla parte del collaboratore ci deve essere l’impegno di pubblicizzare il file presente sul nostro sito, così da. aumentarne

Algoritmi : i più comuni algoritmi (find, sort, count ...) che, per mezzo degli iteratori si applicano a qualsiasi container. Interfaccia di alto livello verso le