Sei sulla pagina 1di 51

I Template in C++

Corso di
Linguaggi di Programmazione ad Oggetti 1

a cura di:
Giancarlo Cherchi
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
Introduzione
Consentono la creazione di:

l Funzioni generiche
l Classi generiche

Ovvero: il tipo dei dati su cui operano viene


specificato come parametro
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 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 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 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 generiche
l Definiscono una serie di operazioni generali
applicabili 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 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 generiche
La forma tipica della definizione di una funzione
generica (o template) è:

template <typename tipo>


tipo-restituito nome-funzione (parametri)
{
// corpo della funzione
}
Funzioni 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 parametri
della funzione, in quanto il compilatore istanzia
le funzioni template sulla base dei parametri
attuali specificati al momento della chiamata
Un esempio…
template <typename X>
void swap(X & a, X & b)
{
X temp;
temp = a;
a = b;
b = temp;
}
Un esempio…
void main()
{
int i = 10, j = 20;
char a = ‘A’, b = ‘B’;

swap (i, j);


swap (a, b);
}
Terminologia
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ù 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ù 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);
}
Overloading esplicito
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 overloading
piuttosto che funzioni generiche
Esempio
template <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 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 template
l I distruttori non possono essere template
l Non possono essere utilizzate specifiche di link
diverse da C++ (extern “lang” prot)
Uso delle funzioni generiche
l Possono essere applicate a varie situazioni
l 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: BubbleSort generico
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: BubbleSort generico
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 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 stessi
algoritmi
Classi 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: definizione
l tipo è un segnaposto a cui verrà sostituito il nome
del tipo al momento della creazione di un’istanza
della classe
l E’ possibile dichiarare una classe generica che
utilizza più tipi generici mediante un elenco
separato da virgole
Classi generiche: istanze
l Dopo aver definito una classe generica, è
possibile crearne una specifica istanza usando la
forma:

nome-classe<tipo> oggetto;
Classi 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 template
class stack_int { class stack_float {
int dim, pos; int dim, pos;
int *vet; float *vet;
public: public:
stack_int (int max) { stack_int (int max) {
dim=max; pos=-1; dim=max; pos=-1;
vet=new int[max]; } vet=new float[max]; }
void push (int elm); void push (float elm);
int top(); float top();
}; };
Esempio con template
template <typename T> void main() {
class stack { stack<int> sti;
int dim, pos; stack<float> stf;
T *vet; stack<char *> stc;
public:
stack (int max) {
sti.push(25);
dim=max; pos=-1;
vet=new T[max]; } stf.push(3.14f);
void push (T elm); int n = stf.top();
T top(); stc.push (“stringa!”);
}; }
Esempio 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 static
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 template
equivale ad una classe ordinaria
Osservazioni su static
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 particolare
istanza di classe template ci si vuole riferire.
La forma generale è:

template <typename T>


classe-template<T>::membro-static = valore;
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”
template<typename T> template<typename T>
class Vector { Vector<T>::Vector(int len) {
T* elm; int dim; elm = new T[dim = len];
public: }
Vector (int = 100);
template<typename T>
~Vector();
Vector<T>::~Vector() {
T& operator[](T &); delete [] elm;
/* … */ }
};
template<typename T>
T & Vector<T>::operator[] (T& n) {
return n;
}
Dichiarazioni 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 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 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 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 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.
Keyword typename
l Il significato di “typename” (racchiuso tra <>)
all’interno di una dichiarazione template è quella
di indicare qual è il nome di tipo che è parametro
del template
l Prima della standardizzazione di typename, si
utilizzava con lo stesso significato la keyword
class (indicante al compilatore che il nome che
segue è un tipo):

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


Keyword typename
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?
Keyword typename
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 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 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 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
Template ed ereditarietà
l E’ possibile derivare una classe a partire da una
istanza di template:

class Deposito : public Magazzino<Scocche> {


/* … */
};
Template 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)


Template ed ereditarietà
l Infine, è anche possibile derivare classi generiche da
classi base anch’esse generiche:

template<T> class Deposito : public Magazzino<T> {


/* … */
};
Template ed Ereditarietà
Osservazione:
tra le istanze dei template NON vi è relazione di
discendenza anche se vi è tra i parametri

Esempio: se si dichiara
Magazzino<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 osservazioni
l Ereditarietà e Template sono strumenti diversi
che 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 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