Upload
truonglien
View
218
Download
0
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