• Aucun résultat trouvé

Les modèles, appelés également patrons, permettent de créer des fonctions ou des classes génériques qui peuvent être paramétrées de point de vue types des données qu'elles manipulent. Ces modèles permettent ainsi d'avoir une écriture plus concise du code puisqu'un modèle particulier peut remplacer plusieurs définitions de fonctions ou de classes.

Les modèles de fonctions

• Un modèle de fonction, appelé également patron de fonction ou fonction générique est une fonction dont la définition utilise un ou plusieurs types paramétrables. Le type effectif qui sera utilisé est déterminé au moment de l'appel de la fonction générique.

• Par exemple si une fonction va effectuer les mêmes traitements (même algorithme) mais sur des données de types différents alors au lieu de surcharger une telle fonction, il est possible de la définir en tant que modèle puis instancier le type exact au moment de l'appel de cette dernière.

Déclaration du paramètre de type

La déclaration d'un paramètre représentant un type T , appelé également paramètre de type se fait de la manière suivante :

template <class T>

• template indique que l'on est en train de créer un modèle.

• class T est une déclaration du nom du paramètre qui va désigner le type. Le nom de ce paramètre est dans ce cas T.

Définition d'une fonction générique

La définition d'une fonction générique qui utilise des paramètres de type se fait de la manière suivante :

template <class T> TypeRetour NomFonction(Paramètres) {

// Corps }

Exemple :

On considère la fonction Max qui renvoie le maximum entre deux éléments. Ces deux éléments peuvent être d'un type quelconque : int, char, float, double, etc. Au lieu de proposer une définition de la fonction Max pour chacun de ces types, il est possible de définir une seule version qui servira dans ce cas de modèle.

Les modèles Programmation orientée objet (C++) _________________________________________________________________________________________________________________

template <class T> T Max(T a, T b) { if(a>b)

return a; return b;

}

Appel d'une fonction générique

La syntaxe de l’appel d’une fonction générique se présente comme suit :

NomFonction<TypeEffectif>(arguments);

Exemple :

L’appel suivant renvoie le maximum de deux entiers :

int c ;

c = Max<int>(5,19) ;

Toutefois, le compilateur est capable en analysant la signature de l’appel de connaître implicitement le ou les types effectifs utilisés. De ce fait, l'appel de la fonction générique peut se faire comme pour les fonctions classiques en lui passant des paramètres effectifs sans spécifier explicitement l’argument du type.

NomFonction(arguments);

Exemple :

template <class T> T Max(T a, T b) { if(a>b) return a; return b; } int main() { int a=5, b=9, c; c=Max(a,b); double m=3.5, n=2.9, k; k=Max(m,n);

cout<<"Le max des entiers est : "<<c; cout<<"Le max des réels est : "<<k; return 0;

}

Remarque :

Au moment de l’appel, le compilateur va générer une nouvelle instance de la fonction générique dans laquelle il remplace le type générique T par le type effectif qui lui correspond dans l'appel. Cette nouvelle instance représente la fonction qui sera véritablement exécutée au moment de l'appel. Dans l’exemple précédent le compilateur génère deux instances de la fonction Max qui correspondent aux prototypes suivants :

• int Max(int; int) pour le premier appel.

Les modèles Programmation orientée objet (C++) _________________________________________________________________________________________________________________

Remarque :

• Le compilateur génère autant d'instances à partir d'un modèle qu'il y a d'appels avec des types effectifs différents.

• La fonction générique va servir essentiellement pour la synthèse des fonctions qui seront véritablement appelées. C'est le code binaire de ces dernières qui va figurer dans l'exécutable généré. Dans cet exécutable le code générique n'a aucune existence.

Utilisation de plusieurs paramètres de type

Il est possible d'utiliser plus qu'un paramètre de type dans une fonction générique. Dans ce cas, ces paramètres doivent être déclarés chacun avec le mot réservé class et séparés par des virgules. L'exemple suivant montre le prototype d'une fonction générique qui utilise deux paramètres de type distincts :

Exemple :

template <class T1, class T2> T2 F(T1 a, T2 b);

La fonction F prend un premier paramètre de type T1 et un deuxième paramètre de type T2.

T1 et T2 sont dans ce cas génériques et seront instanciés au moment de l'appel de F.

Utilisation des paramètres classiques dans un modèle de fonctions

Il est tout à fait possible de combiner dans le prototype d'un modèle de fonctions des paramètres de type avec des paramètres classiques pour lesquels le type est connu. L'exemple suivant donne une illustration de cette situation.

Exemple :

template <class T> int CompteZero(T* Tab, int N) { int NbZeros = 0;

for(int i=0;i<N; i++) if(!Tab[i])

NbZeros++; return NbZeros;

}

int N est dans ce cas un paramètre classique.

Surdéfinition d'une fonction générique

Tout comme les fonctions classiques, il est tout à fait possible de surdéfinir une fonction générique. Cette surdéfinition peut être elle-même une fonction générique comme elle peut comporter des paramètres classiques.

Exemple :

L'exemple suivant montre deux surdéfinitions possibles de la fonction générique Max une générique et l'autre non :

// Surdéfinition générique de la fonction Max template <class T> T Max(T a, T b, T c)

{

return Max(Max(a,b),c); }

Les modèles Programmation orientée objet (C++) _________________________________________________________________________________________________________________

// Surdéfinition comportant des paramètres classiques (int N) template <class T> T Max(T* Tab, int N)

{ T MaxVal = Tab[0]; for(int i=1;i<N;i++) if(Tab[i]>MaxVal) MaxVal = Tab[i]; return MaxVal; }

Spécialisation d'une fonction générique

Il y a des situations où l'algorithme d'une fonction générique s’avère inadapté pour certains types d'arguments. Dans ce cas, il est possible de spécialiser la fonction générique en ajoutant des versions adaptées à ces types tout en conservant le modèle du prototype (il ne s'agit pas d'une surdéfinition).

Exemple :

La fonction générique Max telle qu'elle est définie précédemment n'est pas adaptée pour la comparaison des chaînes parce qu'une telle comparaison ne se fait pas à l'aide de l'opérateur > mais avec la fonction strcmp. Il est dans ce cas possible d'ajouter une spécialisation de la fonction générique pour ce cas particulier.

template <class T> T Max(T a, T b) { if(a>b)

return a; return b;

}

// Spécialisation de Max char* Max(char* a, char* b) { if(strcmp(a,b)>0) return a; return b; } ……… char Ch1[20]="Programme"; char Ch2[20]="Algorithme";

cout<<Max(Ch1,Ch2); // C’est la spécialisation qui est utilisée ………

Dans ce cas le compilateur va commencer sa recherche par la fonction qui correspond exactement à la signature de l'appel et va par conséquent directement utiliser la spécialisation sans avoir besoin d'analyser la fonction générique afin de synthétiser une version adaptée. Remarque :

Il n'y a pas de moyens de limitation des types qui peuvent être utilisés avec une fonction générique. Par conséquent, c'est à celui qui fait l'appel de vérifier si le modèle est adapté aux types des arguments qu'il utilise. Par exemple la fonction générique Max peut accepter n'importe quel type y compris les types personnalisés comme les classes. Si des classes sont passées à Max alors, il faut vérifier que l'opérateur > est bien surchargé pour de telles classes.

Les modèles Programmation orientée objet (C++) _________________________________________________________________________________________________________________