Vous êtes sur la page 1sur 8

LA GENERICITE

LA GENERICITE

Plan du chapitre :

I. Définition …………………………………………………………………………………………………
………………………………………………………………………………………………………. 87
II. Fonctions génériques ….………………………………………………………………………………
…………………………………. 87
III. Classes génériques ………………………………………………...……………………
……………………… …………………………………... 91

Objectifs du chapitre :
 Comprendre la notion de généricité en C++.
 Connaître
tre et manipuler des fonctions et classes génériques avec le langage C++.
C++

BEN ROMDHAN Mourad 86


LA GENERICITE

I. Définition:
Parmi les techniques présentées pour améliorer la réutilisabilité, nous avons présenté la notion de
généricité, qui permet de paramétrer les fonctions et les classes par un type de données.
La généricité permet d'avoir des fonctions et des classes paramétrables, c'est-à-dire que, au
moment où nous en avons besoin, nous précisons le type à utiliser pour ladite fonction ou ladite
classe. C'est le type qui est paramétrable. Ce concept nous permettra d'avoir des écritures plus
concises et ainsi d'éviter de nombreuses surdéfinitions.
La généricité est souvent appelée « template » ( patron – évocation de la haute couture), ou
également « modèle ».

II. Fonctions génériques:


II.1. Exemple de création et d’utilisation d’un patron de fonctions:
Une fonction générique n'est pas utilisable directement, on l'appelle patron de fonction (template).
La fonction générique est ensuite instanciée par le type de paramètres qu'elle accepte.
Prenons un exemple concret. Supposons que l’on souhaite écrire une fonction min qui accepte
deux paramètres et qui renvoie la plus petite des deux valeurs qui lui est fournie. On désire
bénéficier de cette fonction pour certains types simples disponibles en C++ (soit int et double). La
première solution pour atteindre ce but est d’utiliser la surcharge et de définir 2 fonctions min, une
pour chacun des types considérés :

int min (int a, int b)


{
return ((a<b) ? a:b);
}
Même code
double min (double a, double b)
{
return ((a<b) ? a:b);
}
void main()
{
double y = min(3.2,2.5);
int z = min( 4 , 0);
}

Lors d’un appel à la fonction min, le type des paramètres est alors considéré et l’implantation
correspondante est finalement appelée. Ceci présente cependant quelques inconvénients :
 La définition des 2 fonctions mène à des instructions identiques, qui ne sont différenciées que
par le type des variables qu’elles manipulent. On s’aperçoit ici que plus qu’une fonction, on
souhaiterait exprimer une méthode, valable pour n’importe quel type manipulé : la fonction min
est la fonction qui renvoie le plus petit des paramètres qui lui est fourni. Cet élément est
déterminé grâce à l’opérateur < qui établit une relation d’ordre sur le type d’élément considéré.
 Si on souhaite étendre la définition de cette fonction à de nouveaux types, il faut définir une
nouvelle implantation de la fonction min par type considéré.
Une autre solution est de définir une fonction template, c’est-à-dire générique. Cette définition
définit en fait un patron de fonction, qui est instancié par un type de données (ici le type T) pour
produire une fonction par type manipulé.

BEN ROMDHAN Mourad 87


LA GENERICITE

template <class T>


T min (T x, T y)
{
return ((x<y) ? x:y);
}
void main()
{
double y = min(3.2,2.5);//double min(double, double)
int z = min( 4 , 0); // int min(int, int)
}

Il n’est donc plus nécessaire de définir une implantation par type de données. De plus, la fonction
min est valide avec tous les types de données dotés de l’opérateur <. On définit donc bien plus
qu’une fonction, on définit une méthode permettant d’obtenir une certaine abstraction en
s’affranchissant des problèmes de type.

Remarques :
 Il est possible de définir des fonctions template acceptant plusieurs types de données en
paramètre. Chaque paramètre désignant une classe est alors précédé du mot-clé class, comme
dans l’exemple: template <class T, class U> ....
 Chaque type de données paramètre d’une fonction template doit être utilisé dans la définition
de cette fonction.
 Pour que cette fonctionnalité soit disponible, les fonctions génériques doivent être définies dans
des fichiers d’interface (fichiers .H). Les fonctions template sont en effet expansées elles aussi.
Ainsi, chaque appel fait à ce genre de fonctions est remplacé, à la pré-compilation, par le code
source correspondant à la fonction.

II.2. Identification des paramètres de type d’une fonction patron:


Les exemples précédents étaient suffisamment simples pour que l’on « devine » quelle étaient la
fonction instanciée pour un appel donné. Mais, reprenons le patron min avec ces déclarations:

int n ; char c ;

Que va faire le compilateur en présence d’un appel tel que min (n,c) ou min (c,n) ? En fait, la règle
prévue par C++ dans ce cas est qu’il doit y avoir correspondance absolue des types. Cela signifie
que nous ne pouvons utiliser le patron min que pour des appels dans lesquels les deux arguments
ont le même type. Manifestement, ce n’est pas le cas dans nos deux appels, qui aboutiront à une
erreur de compilation.

On notera que, dans cette correspondance absolue, l’éventuel qualificateur const intervient.

BEN ROMDHAN Mourad 88


LA GENERICITE

Voici quelques exemples d’appels de min qui précisent quelle sera la fonction instanciée lorsque
l’appel est correct:
int n ; char c ; unsigned int q ;
const int ci1=10, ci2=12 ;
int t[10] ;
int *adr=n ;
min(n,c) ;//erreur
min(n,q) ; //erreur
min(n, ci1) ; //erreur :const int et # de int
min(ci1, ci2) ;// min(cons tint, cons tint)
min(t,adr) ;//min (int *, int *)
Il est cependant possible d’intervenir sur un mécanisme d’identification de type. En effet, C++
vous autorise à spécifier un ou plusieurs paramètres de type au moment de l’appel du patron.
Voici quelques exemples utilisant les déclarations précédentes :
min<int>(c,n)
 force l’utilisation de min<int>, et donc la conversion de c en int ; le résultat sera de type int
min<char>(q,n)
 force l’utilisation de min<char>, et donc la conversion de q et n en char ; le résultat sera de
type char
Autre exemple :
template <class T, class U> T fct(T x,U y,T z)
{
return x+y+z ;
}
...
int n,p ; float x ;
cout << fct<int ,float>(n, p, x); n et x: int et p: float

II.2. Surdéfinition de patrons:


Problème:
char *c1= "az";
char *c2= "bz";
char *c3= min (c1,c2);

 la comparaison avec l'opérateur < retourne la plus petite adresse !!


 Si le type de données à comparer n'est pas doté de l'opérateur <, le résultat sera erroné.
Solution :

 A côté du patron de classe, il faut écrire une spécialisation de la fonction min pour le type
char *.
template <class T>
T min (T x, T y)
{
return ((x<y) ? x:y);
}
char * min (char * c1, char * c2)
{
return ((strcmp(c1,c2)<0)?c1:c2);
}

BEN ROMDHAN Mourad 89


LA GENERICITE

De même il est possible de surdéfinir une fonction classique, il est possible de surdéfinir un patron
de fonctions, c’est-à-dire de définir plusieurs patrons possédant des arguments différents. On
notera que cette situation conduit en fait à définir plusieurs « familles » de fonctions.
Exemple:
//Patron numero I
template <class T> T min (T x, T y)
{
return ((x<y) ? x:y);
}
//Patron numero II
template <class T> T min (T x, T y, T z)
{
return min(min(x,y),z);
}

II.3. Appel d'une fonction template dans une classe :


Exemple:
class chaine
{
char * ch;
public:
chaine (char *);
chaine operator < (chaine &);
chaine & operator = (const chaine &);
~chaine ();
};
chaine chaine :: operator< (chaine& S)
{
chaine Res(min(ch, s.ch));
return Res;
}
void main ()
{
chaine c1("bz"), c2("az"), c3("");
c3=min(c1,c2);
}

II.4. Application à une classe :


Pour pouvoir appliquer le patron min à une classe, il est bien sûr nécessaire que l’opérateur <
puisse s’appliquer à deux opérandes de ce type classe. Voici un exemple dans lequel nous
appliquons min à deux objets de type vect, classe munie d’un opérateur < fournissant un résultat
basé sur le module des vecteurs :

template <class T> T min (T x, T y)


{
return ((x<y) ? x:y);
}
class vect
{
int x,y ;
public :

BEN ROMDHAN Mourad 90


LA GENERICITE

vect(int,int) ;
void afficher() ;
int operator <(vect v)
{
return x*x+y*y <v.x*v.x+v.y*v.y ;
}
};
void main()
{
vect v1(3,2),v2(4,1), v ;
v=min(v1, v2) ;
v.afficher() ;// affichage de v1(3,2)
}

III. Classes génériques:


Il est possible, comme pour les fonctions, de définir des classes template, c’est-à-dire paramétrées
par un type de données. Cette technique évite ainsi de définir plusieurs classes similaires pour
décrire un même concept appliqué à plusieurs types de données différents. Elle est largement
utilisée pour définir tous les types de containers (comme les listes, les tables, les piles, etc.), mais
aussi des algorithmes génériques par exemple. La bibliothèque STL (chapitre 9) en particulier
propose une implantation d’un bon nombre de types abstraits et d’algorithmes génériques.

III.1. Création d’un patron de classes:


On écrit la classe en la paramétrant par un type de données,
Exemple:
template <class T>
class Tableau //tableau de tout type
{
T tab[10];
public:
T & operator [] (int);
}
template <class T>
T & Tableau <T> :: operator[] (int i)
{
return tab[i];
}
void main()
{
Tableau <int> A;
// objet Tableau de 10 entiers
A[0]=1;
Tableau <char> B;
// objet Tableau de 10 caractères
B[0]='x';
}
class complexe
{ float reel, imag;
public:
complexe (float=0.0, float=0.0);
};

BEN ROMDHAN Mourad 91


LA GENERICITE

void main()
{
Tableau <complexe> A;
A[0]=complexe(1,2);
}
Remarques :
 Comme dans le cas des fonctions template, tout le code source correspondant à des classes
template (y compris la définition de leurs méthodes) doit se trouver dans l’interface de la
classe correspondante.
 Une classe template permet de définir des attributs, des paramètres ou des valeurs de retour de
méthodes template. De façon réciproque, pour pouvoir définir des entités template à
l’intérieur d’une classe, la classe doit elle-même être template.
 Attention à la syntaxe des méthodes template définies en dehors du corps de la classe. La
définition se fait :
template <class T> typeRetour nomClasse<T>::nomMéthode(params).

Valeur par défaut:

template <class T=int>


class Tableau //tableau de tout type
{ T tab[10];
public:
T & operator [] (int);
}
template <class T>
T & Tableau <T> :: operator[] (int i)
{
return tab[i];
}
void main()
{ Tableau A;
// objet Tableau d’entiers
Tableau <complexe> B;
// objet Tableau de complexes
Tableau <char> C;
// objet Tableau de caractères
}

Exemple : Pile générique:


template <class T=int>
class pile
{ int max;
T * tab;
int sommet;
public:
pile (int=10);
~pile (){delete[]tab;}
pile(const pile &);
pile & operator = (const pile &);
void empiler (T);
void depiler (T &);
bool estvide (){return sommet==-1};
bool estpleine (){return sommet==max-1};
};

BEN ROMDHAN Mourad 92


LA GENERICITE

template <class T>


pile<T>::pile(int taille)
{
max=taille;
sommet=-1;
tab=new T[max];
}
template <class T>
pile<T>::pile(const pile<T> &p)
{
max=p.max;
sommet=p.sommet;
tab=new T[max];
for(int i=0;i<=sommet;i++)
tab[i]=p.tab[i];
}
template <class T>
pile<T> & pile<T>::operator=(const pile<T> &p)
{
if (this !=&p)
{
delete [] tab;
max=p.max;
sommet=p.sommet;
tab=new T[max];
for(int i=0;i<=sommet;i++)
tab[i]=p.tab[i];
}
return *this;
}
template <class T>
void pile<T>::empiler(T x)
{
if (!estpleine())
tab[++sommet]=x;
}
template <class T>
void pile<T>::depiler(T &x)
{
if (!estvide())
x=tab[sommet--];
}
void main()
{
pile p;//p est une pile de 10 entiers
p.empiler(5);
...
pile<complexe> p1(5);//p1 est une pile de 5
//complexes à condition d'avoir une classe
//complexe!!!
p1.empiler(complexe(4,2));
...
}

BEN ROMDHAN Mourad 93

Vous aimerez peut-être aussi