Pour mieux comprendre on va étudier le premier exemple. Supposons que nous voulions créer des listes ``génériques''. Avec ou sans patrons, nous avons plusieurs solutions :
On peut, d'abord, utiliser l'héritage. A partir de listes générales :
struct Lien{ Lien * suivant; }; class Liste{ //... void inserer(Lien *); //... };On peut définir des listes d'
int
par exemple :
struct Lien_Int : Lien { int data; }; class Liste_Int:public Liste{ //... void inserer(Lien_Int *l){ Liste::inserer(l); } //... };Cette méthode partage et réutilise effectivement le code, mais elle présente plusieurs inconvénients comme, par exemple, d'obliger à réécrire une partie du code comme Liste::inserer(l); pour la fonction inserer pour chaque nouveau type de liste.
Comme le types pointeurs sont ``universels'', on peut réaliser des listes de pointeurs sur void et par conversions créer des listes de pointeurs sur n'importe quel type. C'est une solution possible en C++. Son inconvénient majeur réside dans l'absence de contrôle du typage.
Une autre solution consiste à utiliser le préprocesseur :
on ``réaliserait'' l'instanciation en changeant le
#define TYPE
, puis en recompilant les programmes.
#define TYPE int struct Lien{ Lien * suivant; TYPE data; }; class Liste{ //... void inserer(Lien *); //... };L'inconvénient de cette solution est de reposer sur le préprocesseur : en fait, on sera obliger de recréer complètement un nouveau code pour chaque nouveau type (contrairement à la solution précédente). De plus, faire ``coexister'' plusieurs types de listes sera un peu délicat car nécessitera de choisir des noms différents pour chacun de ces types. Cependant, la solution du C++ n'est pas très lointaine de cette approche.
Avec les patrons on pourrait procéder ainsi :
template <class T> struct Lien{ Lien * suivant; T data; }; template <class T> class Liste{ //... void inserer(Lien<T> *); //... };La classe Liste n'existe pas en tant que telle
class X{ //... }; Liste<int> li; Liste<X *> lxp; //...Remarquons aussi, que chaque instanciation créera une nouvelle classe entièrement distincte, il n'y aura pas de partage (réel) de code entre ces nouvelles classes. De fait, aux vérifications de types près (ce qui est très important), tout se passe comme avec l'utilisation du préprocesseur : pour chaque instanciation, un nouveau code sera créé.
Il s'agit en quelque sorte d'une facilité d'écriture et d'un mécanisme purement syntaxique : il n'y a pas création d'une nouvelle sorte de type, mais comme usuellement, tous les types (ainsi que les fonctions membres) sont déterminés à la compilation.