51
I I Template Template in C++ in C++ Corso di Linguaggi di Programmazione ad Oggetti 1 a cura di: Giancarlo Cherchi

Corso di Linguaggi di Programmazione ad Oggetti 1armano/LPO1/pdf/template.pdf · Corso di Linguaggi di Programmazione ad Oggetti 1 a cura di: Giancarlo Cherchi. Introduzione Cosa

Embed Size (px)

Citation preview

I I TemplateTemplate in C++in C++

Corso di Linguaggi di Programmazione ad Oggetti 1

a cura di:Giancarlo Cherchi

Introduzione Introduzione

Cosa sono i Template?

TEMPLATE ? MODELLI

l Funzionalità non presenti nelle prime versioni del C++

l Aggiunti a partire dal ’90 e ora definiti nello standard C++ ANSI

IntroduzioneIntroduzione

Consentono la creazione di:

l Funzioni generichel Classi generiche

Ovvero: il tipo dei dati su cui operano viene specificato come parametro

Informazioni generali Informazioni generali

Vantaggi principali:

l Si possono utilizzare funzioni e/o classi con dati diversi senza ricodificare esplicitamente una versione diversa per ogni diverso tipo di dati.

l Rendono possibile il riutilizzo del codice

NOTA: il riutilizzo avviene in modo “opposto” al meccanismo dell’ereditarietà

Informazioni generaliInformazioni generali

Ereditarietà:

l Dato un insieme di valori, riconoscerne le proprietà generali e inserirle in una classe base astratta.

l Se gli oggetti sono un caso particolare di qualche classe della gerarchia, si specializza la classe più opportuna.

Quindi: dai valori alle proprietà

Informazioni generaliInformazioni generali

Programmazione generica:

l Definire a priori le proprietà e scrivere codice che lavora su tipologie (non note) di oggetti che soddisfano tali proprietà

l Riutilizzare il codice quando si hanno nuove tipologie di oggetti che soddisfano le stesse proprietà

Quindi: dalle proprietà ai valori

Informazioni generaliInformazioni generali

l Il meccanismo dei template rende disponibile la tecnica della programmazione generica

l Un template non è altro che codice parametrico

Anche le funzioni ordinarie usano parametri, ma…

l Con i template i parametri possono essere di vario tipo, mentre nelle funzioni ordinarie devono essere unicamente di un certo tipo

Funzioni genericheFunzioni generiche

l Definiscono una serie di operazioni generaliapplicabili a vari tipi di dati

l Il tipo dei dati su cui dovranno operare è specificato tramite un parametro

l Esistono algoritmi logicamente identici ma che possono operare su tipi diversi (esempio: ordinamento, scambio, min…)

l Con le funzioni generiche è possibile definire la natura dell’algoritmo indipendentemente dai dati

Funzioni genericheFunzioni generiche

l Il codice corretto per il tipo di dati effettivamente utilizzato sarà generato automaticamente dal compilatore

l Una funzione generica è quindi una funzione in grado di effettuare “automaticamente” l’overloading di sé stessa

Quindi: con un’unica definizione si hanno a disposizione versioni diverse della funzione in grado di operare su differenti tipi di dati

Funzioni genericheFunzioni generiche

La forma tipica della definizione di una funzione generica (o template) è:

template <typename tipo>tipo-restituito nome-funzione (parametri){

// corpo della funzione}

Funzioni genericheFunzioni generiche

l tipo non è altro che un simbolo, un “segnaposto” che il compilatore sostituirà col vero tipo dei dati durante la creazione di una versione specifica della funzione

l Deve essere utilizzato nella lista dei parametridella funzione, in quanto il compilatore istanziale funzioni template sulla base dei parametri attuali specificati al momento della chiamata

Un esempio…Un esempio…

template <typename X> void swap(X & a, X & b){

X temp;temp = a;a = b;b = temp;

}

Un esempio…Un esempio…

void main(){

int i = 10, j = 20;char a = ‘A’, b = ‘B’;

swap (i, j);swap (a, b);

}

TerminologiaTerminologia

l Una funzione generica viene anche chiamata funzione template

l Quando il compilatore crea una versione specifica di una funzione generica, si dice che ha creato una funzione generata

l L’atto di generazione si dice istanziazione

Quindi: una funzione generata è una specifica istanza di una funzione template

Funzioni generiche con più tipiFunzioni generiche con più tipi

l E’ possibile definire più di un tipo di dati generico.

Esempio:

template <typename T1, typename T2>void myfunc (T1 x, T2 y) {

cout << x << “ “ << y << endl;}

Funzioni generiche con più tipiFunzioni generiche con più tipi

l Con le definizioni precedenti, nel main si potrà scrivere:

void main () {int a = 7;char b = ‘F’;

myfunc (a, “prova”);myfunc (10.2, b);

}

OverloadingOverloading esplicitoesplicito

l E’ possibile eseguire l’overloading esplicito di una funzione generica

l La versione modificata tramite overloading, nasconderà la funzione generica relativa a quella specifica versione

NOTA: Tuttavia, se è necessario impiegare versioni diverse di una funzione per molti tipi di dati, in generale è meglio utilizzare funzioni modificate tramite overloadingpiuttosto che funzioni generiche

EsempioEsempiotemplate <typename X> void swap (X & a, Y & b){/* … */ }

void swap (int &a, int &b) {/* … */

cout << “versione modificata…” << endl;}

void main() {int i = 5, j = 10;char a = ‘A’, b = ‘B’;swap (i, j); // Versione modificata!swap (a, b); // Istanza di funzione generica

}

Limitazioni e restrizioniLimitazioni e restrizioni

l Una funzione generica deve eseguire le stesse operazioni generali su tutte le sue versioni (a differenza di una funzione modificata tramite overloading)

l Una funzione virtuale non può essere templatel I distruttori non possono essere templatel Non possono essere utilizzate specifiche di link

diverse da C++ (extern “lang” prot)

Uso delle funzioni genericheUso delle funzioni generiche

l Possono essere applicate a varie situazionil In particolare, una funzione che definisce un

algoritmo generalizzabile può essere trasformata in una funzione template

l In questo modo, potrà essere utilizzata con qualsiasi tipo di dati senza necessità di ricodifica

Esempio: Esempio: BubbleSortBubbleSort genericogenerico

template <typename X>void bubble (X * items, int dim) {

for (int a = 1; a < dim; ++a)for (int b = dim-1; b >= a; --b)

if (items[b-1] > items[b])swap(items[b], items[b-1]);

}

Esempio: Esempio: BubbleSortBubbleSort genericogenerico

void main() {int iarray[7] = {7,5,4,3,9,8,6};double darray[5] = {4.3,2.5,-0.9,10.1,3.2};

bubble (iarray, 7);bubble (darray, 5);

}

Classi genericheClassi generiche

l Una classe generica è in grado di contenere algoritmi per i quali il tipo dei dati da manipolare è specificato al momento della creazione di ogni elemento della classe

l Sono utili quando una classe contiene operazioni logiche generalizzabili.

Esempio: strutture dati di tipo stack di interi, caratteri o stringhe, funzionano con gli stessialgoritmi

Classi generiche: definizioneClassi generiche: definizione

La forma tipica della dichiarazione di una classe generica è:

template <typename tipo> class nome-classe {// dichiarazioni degli slot// prototipi dei metodi

}

Classi generiche: definizioneClassi generiche: definizione

l tipo è un segnaposto a cui verrà sostituito il nomedel tipo al momento della creazione di un’istanzadella classe

l E’ possibile dichiarare una classe generica che utilizza più tipi generici mediante un elenco separato da virgole

Classi generiche: istanzeClassi generiche: istanze

l Dopo aver definito una classe generica, è possibile crearne una specifica istanza usando la forma:

nome-classe<tipo> oggetto;

Classi genericheClassi generiche

l Per definire i metodi, bisogna dichiarare per ogni membro che si tratta di codice relativo ad un template e specificare affianco ai simboli di classe il parametro del template

l Un template può avere un numero qualunque di parametri, che possono anche essere di un tipo standard (int, float…)

Esempio senza Esempio senza templatetemplate

class stack_int {int dim, pos;int *vet;

public:stack_int (int max) {dim=max; pos=-1;vet=new int[max]; }

void push (int elm);int top();

};

class stack_float {int dim, pos;float *vet;

public:stack_int (int max) {dim=max; pos=-1;vet=new float[max]; }

void push (float elm);float top();

};

Esempio con Esempio con templatetemplate

template <typename T>class stack {

int dim, pos;T *vet;

public:stack (int max) {dim=max; pos=-1;vet=new T[max]; }

void push (T elm);T top();

};

void main() {stack<int> sti;stack<float> stf;stack<char *> stc;

sti.push(25);stf.push(3.14f);int n = stf.top();stc.push (“stringa!”);

}

Esempio con più parametriEsempio con più parametri

template <typename Key, typename Value, int size>class AssocArray {

static const int kSize;Key keyArray [size];Value valueArray [size];

public:/* … */

};template <typename Key, typename Value, int size>const int AssocArray <Key, Value, size>::kSize = size;

Osservazioni su Osservazioni su staticstatic

l Nelle classi ordinarie, static consente di avere dei membri (inizialmente nulli per default) che hanno medesimo valore per tutte le istanze della classe

l Il discorso si estende alle classi template, considerando che un’istanza di classe templateequivale ad una classe ordinaria

Osservazioni su Osservazioni su staticstatic

l Così come è possibile assegnare un valore iniziale diverso da zero dall’esterno di un membro static di classe ordinaria, lo stesso discorso si estende alle classi template

l Occorre però specificare a quale particolareistanza di classe template ci si vuole riferire.

La forma generale è:

template <typename T> classe-template<T>::membro-static = valore;

Dichiarazione esterna di metodi Dichiarazione esterna di metodi

l Nella definizione delle funzioni componenti, che si vogliono implementare all’esterno della classe, l’uso corretto del nome della classe segue la forma:

nome-classe<tipo>::

l L’operazione è necessaria perché il compilatore possa associare correttamente il metodo alla corrispondente istanza di classe

Esempio metodi “esterni”Esempio metodi “esterni”

template<typename T>class Vector {

T* elm; int dim;public:

Vector (int = 100);~Vector();T& operator[](T &);/* … */

};

template<typename T>Vector<T>::Vector(int len) {elm = new T[dim = len];}

template<typename T>Vector<T>::~Vector() {

delete [] elm;}

template<typename T>T & Vector<T>::operator[] (T& n) {

return n;}

Dichiarazioni friendDichiarazioni friend

Una classe template può utilizzare la dichiarazione friend in 3 forme:

1) Per dichiarare una specifica funzione o classe friend

2) Per dichiarare friend un intero template di funzioni o classi

3) Per dichiarare friend un template di funzioni o classi senza che vi sia un legame tra la classe che concede il privilegio e il template

Uso di friend: esempio 1Uso di friend: esempio 1

template<typename T>class Magazzino {

friend class Fornitore;friend void Inventario();/* … */

};

La classe Fornitore e la funzione Inventario saranno friend di tutte le istanze di Magazzino che saranno create dal compilatore.

Uso di friend: esempio 2Uso di friend: esempio 2

template<typename T> class Magazzino {friend class Fornitore<T>;friend void Inventario<T>(Magazzino<T>&);/* … */

};Ogni istanza della classe Magazzino, dichiara come friend la corrispondente istanza di Fornitore e la corrisponde istanza della funzione esterna Inventario.

A differenza del caso precedente (in cui potenzialmente c’è corrispondenza uno a molti), la corrispondenza è uno a uno.

Uso di friend: esempio 3Uso di friend: esempio 3

template<typename T>class Magazzino {

template<typename T_nazione>friend class Fornitore<T_nazione>;

template<typename T_nazione>friend void Esposizione<T_nazione> (Magazzino<T>

&);/* … */ };

Magazzino<Scocche> torino;Fornitore<Francia> simca;

Uso di friend: esempio 3Uso di friend: esempio 3

l Questo esempio descrive una situazione in cui a una particolare istanza di Magazzino (esempio Magazzino<Scocche>) vengono fatte corrispondere una pluralità di istanze della classe Fornitore e della funzione Esposizione()

l Una pluralità di classi fornitore, create sulla base della nazionalità, saranno considerate friend di ciascun tipo di magazzino.

KeywordKeyword typenametypename

l Il significato di “typename” (racchiuso tra <>) all’interno di una dichiarazione template è quella di indicare qual è il nome di tipo che è parametrodel template

l Prima della standardizzazione di typename, si utilizzava con lo stesso significato la keywordclass (indicante al compilatore che il nome che segue è un tipo):

template<class T> class A { /*…*/ };

KeywordKeyword typenametypename

l Viene inoltre usata per risolvere possibili ambiguità:

template<typename T> class TMyTemp {T::TId Object; };

l T::TId è un identificatore di tipo dichiarato all’interno di T oppure un membro pubblico di T?

KeywordKeyword typenametypename

l Anche se si voleva intendere un tipo, il compilatore assume per default che si tratta di un membro pubblico di T.Per ovviare al problema si usa typename:

template<class T> class TMyTemp {typename T::TId Object; };

l NOTA: typename non equivale a typedef!

Vincoli implicitiVincoli impliciti

l L’istanziazione di un template (classe o funzione) può richiedere che su uno o più parametri sia definita una qualche funzione o operazione.

Esempio:

template<typename T> T & min (T & A, T & B) {

return (A < B) ? A : B; }

Vincoli implicitiVincoli impliciti

Se il tipo T viene sostituito con un tipo non standard, ad esempio una classe definita dall’utente, il compilatore potrebbe segnalare un errore.

Esempio:

T1 a, b;min (a, b); // qui viene segnalato un errore se non è stato ridefinito l’operatore ‘<‘ per il tipo T1

Vincoli implicitiVincoli impliciti

l L’errore può essere segnalato solo al momento dell’istanziazione del template!

l Purtroppo, il linguaggio C++ segue la via dei vincoli impliciti, ovvero non fornisce alcun meccanismo per esplicitare assunzioni fatte sui parametri dei template

l Tale compito è lasciato ai messaggi d’errore del compilatore e/o ai commenti del programmatore

TemplateTemplate ed ereditarietàed ereditarietà

l E’ possibile derivare una classe a partire da una istanza di template:

class Deposito : public Magazzino<Scocche> {/* … */};

TemplateTemplate ed ereditarietàed ereditarietà

l E’ anche possibile derivare una sottoclasse generica a partire da una classe base normale:

template<T> class Deposito : public Magazzino {/* … */};

(pluralità di depositi derivati da una classe unica)

TemplateTemplate ed ereditarietàed ereditarietà

l Infine, è anche possibile derivare classi generiche da classi base anch’esse generiche:

template<T> class Deposito : public Magazzino<T> {/* … */};

TemplateTemplate ed Ereditarietàed Ereditarietà

Osservazione:tra le istanze dei template NON vi è relazione di discendenza anche se vi è tra i parametri

Esempio: se si dichiaraMagazzino<T> m1; Magazzino<T1> m2;Deposito<T> d1;

NON si ha relazione di discendenza tra d1 e m1, ma solo tra Magazzino e Deposito.In particolare, un puntatore a Magazzino<T> NON potrà mai puntare a un Deposito<T>

Alcune osservazioniAlcune osservazioni

l Ereditarietà e Template sono strumenti diversiche condividono lo stesso fine del riutilizzo del codice già implementato e testato

l OOP e programmazione generica sono due diverse scuole di pensiero relative al modo di implementare il polimorfismo (per inclusione e parametrico, rispettivamente)

Alcune osservazioniAlcune osservazioni

l Entrambi hanno pregi e difetti.In particolare, i template soffrono dei vincoli impliciti e della generazione di eseguibili più grandi.

l I due strumenti non vanno comunque messi in posizione antitetica: si può sempre rimediare ai difetti dell’uno ricorrendo all’altro