Elementi di programmazione ad oggetti a. a. 2009/2010 Corso di Laurea Magistrale in Ingegneria Elettronica Docente: Mauro Mazzieri, Dipartimento di Ingegneria Informatica, Gestionale e dell’Automazione Lezione 7 Template Programmazione generica La programmazione generica utilizza i tipi come parametri Consentono di definire famiglie di funzioni o di classi, che differiscono sulla base di tipi su cui operano Il compilatore genera una specializzazione per ogni combinazione di tipi usata La programmazione generica consente di generare automaticamente del codice a partire da definizioni generali Template di funzione Le funzioni sovraccaricate realizzano funzionalità analoghe su dati diversi I template servono ad effettuare le stesse funzioni su dati diversi Forniscono un meccanismo con cui è possibile preservare la semantica delle funzioni e delle chiamate di funzioni Consentono di generare automaticamente istanze dello stesso algoritmo che operano su dati diversi Una funzione viene solitamente definita come template quando la sua implementazione rimane invariata per un insieme di tipi di dato diversi gestiti Definizione di template template <class T> template <class Item, class Value> template <typename T> segue la definizione della funzione Es. template <class T> T minore(T a, T b) { return a < b ? a : b; } int main() { cout << minore(10, 20); cout << minore(2.45, 1.14); } Definizione di template La parola chiave template è sempre posta all’inizio di una dichiarazione ed una definizione di template È seguita da una lista di parametri racchiusi tra < e > e separata da virgole La lista di parametri non può essere vuota Un parametro può essere costituito Da un tipo Da un valore costante Template di classe I membri di una classe generica sono dichiarati e definiti esattamente come per le classi non generiche Se un membro è definito esternamente, deve essere esplicitamente dichiarato come template Sono molto usati per definire una serie di manipolazioni (contenitore, algoritmi) su dati specificati dal parametro di tipo Template di classe Un template di classe è un classe con uno o più parametri di tipo L’uso tipico è per i contenitori Li si vuole rendere indipendenti dal tipo di oggetto effettivamente contenuto template<class C> class Sequenza { C* s; int size; int n; public: Sequenza(); Sequenza(const C*); Sequenza(const Sequenza&); ~Sequenza(void); C operator[](int) { return s[i]; } }; Non è possibile sovraccaricare il nome di una classe template Template di classe Un template di classe può avere i membri static Ogni specializzazione ha i propri membri Un membro condiviso va messo in una classe base non template I template di funzione possono essere funzioni membro Un template di classe può avere come membri dei template di funzione Template con costanti Un parametro di template può essere costituito da una costante Costanti di tipo intero possono essere utilizzate per fornire dimensioni o limiti template <class C, int maxsize> class Buffer { C v[maxsize]; int size; // … } Si può usare un parametro di template per definire i successivi parametri template <class C, int dim, C val> bool sottoSoglia(C v[dim]) { for (int i = 0; i < dim; i++) if (v[i] < val) return true; return false; } Esempio: template con costante template <typename T, int size> T minore(T (&v)[size]) { T result = v[0]; for (int i = 1; i < size; i++) if (v[i] < result) result = v[i]; return result; } int main() { int a[] = {1, 2, 3, 4}; double b[] = {0.3, 0.8, 7, 12, 0.1}; cout << minore(a) << endl; cout << minore(b) << endl; return 0; } Deduzione degli argomenti di un template Quando viene chiamato un template di funzione, i tipi ed i valori degli argomenti sono determinati sulla base dei tipi e valori della argomenti I tipi degli argomenti non devono corrispondere esattamente, ma le uniche conversioni possibili sono Specializzazione del template Da Lvalue a Rvalue, da array a puntatore, da funzione a puntatore Conversioni di qualificazione const Da classe a classe base Non è possibile sovraccaricare il nome di una classe template Tipi dei parametri Un template dipende solo dalle proprietà dei tipi di parametri Non richiede che i tipi utilizzati come argomenti siano esplicitamente correlati In particolare, non richiede che appartengano ad una specifica gerarchia di ereditarietà Specializzazione Consente di definire implementazioni alternative per un template template<class C> class Sequenza { //… } // specializzazione template<> class Sequenza<int> { //… } Nelle specializzazione sono tolti dalle <> i parametri per cui si fa la specializzazione Possibile specializzazione parziale Specializzazione e implementazione comune Una specializzazione può servire a creare un’implementazione comune per una famiglia di parametri di tipo // template di contenitore generico template<class C> class Vector { //… } // specializzazione di contenitore di puntatori a void template<> class Vector<void*> { //… } // specializzazione per tutti i puntatori template<class C> class Vector<C*> : private Vector<void*> { //… } Sovraccarico dei template di funzione Le specializzazioni generate a partire da un template vengono chiamate utilizzando la risoluzione dei sovraccarichi Il template di funzione può essere sovraccaricato Da altri template che differiscono per numero di parametri Da funzioni senza template Risoluzione del sovraccarico: 1. 2. 3. Tentativo di risoluzione con funzione senza template Se fallisce, il compilatore cerca un template di funzione e se lo trova genera e utilizza la specializzazione Se non trova ancora alcuna corrispondenza o se ci sono corrispondenze multiple, errore Ereditarietà e template È possibile derivare una classe template da una classe tradizionale Si può derivare una classe template da un’altra classe template Si fa per esempio per fornire una implementazione comune Es. derivare da Vector<T> una CheckedVector<T> che effettua controlli sugli indici I template di classe forniscono un’interfaccia comune ad una famiglia Sono una forma di polimorfismo Polimorfismo dinamico: funzioni virtuali Polimorfismo statico o parametrico: template Nota finale È generalmente opportuno effettuare il debug di un funzione o classe su di un tipo concreto, prima di renderla generica