Académique Documents
Professionnel Documents
Culture Documents
Introduction
Une des caractéristiques majeures du langage C++ est l'importance qu'il accorde à la création de
nouveaux types de données, spécifiquement adaptés au problème traité par un programme particulier.
Cette approche soulève une difficulté lorsqu'on cherche à écrire du code très général, que l'on souhaite
pouvoir réutiliser (sans avoir à y apporter la moindre modification) dans des projets très divers. En
effet, les types utilisés par ces projets ne sont pas seulement différents et nombreux, ils sont
indéterminés au moment où l'on écrit le code général. L'écriture de code prenant en charge cette quasi-
infinité de types différents est ce qu'on appelle la programmation générique, et cette approche est
rendue possible en C++ par les patrons (ou, en anglais, templates).
1 - Patrons de fonctions
Un patron de fonction est une sorte de "fonction potentielle" : c'est un canevas, un plan à partir duquel le
compilateur est capable, en fonction des besoins, de générer plusieurs fonctions réelles, qui diffèrent par le type
des objets qu'elles manipulent.
C'est un peu le même problème que dans le cas d'une fonction qui doit être
capable de travailler sur plusieurs valeurs : l'utilisation d'un paramètre procure
une abstraction de la valeur, ce qui permet à un programmeur ignorant tout de
cette valeur de la désigner en utilisant simplement le nom du paramètre. Les
patrons peuvent être décrits comme un moyen de paramétrer les types, et non
simplement les valeurs.
La syntaxe permettant d'introduire cette "abstraction de type" est assez simple : il faut annoncer que la
définition qui va suivre est celle d'un patron, puis indiquer entre chevrons les "arguments de patron" qui
vont être utilisés. La déclaration d'un argument de patron ressemble beaucoup à celle d'un argument de
fonction : le nom choisi est simplement précédé d'une indication sur la nature de l'objet désigné.
Comme l'objet en question n'est ni une valeur, ni une variable, ni quoi que ce soit de ce genre, on utilise
un mot spécial, typename, qui indique clairement que le nom en cours de définition désignera un type.
Le mot typename peut être remplacé par le mot class (1 template <class T>), mais cette
tolérance (conservée pour des raisons purement historiques) nuit à l'intelligibilité du code, puisque le type en
question peut très bien ne pas être une classe mais un type prédéfini ou énuméré.
Une fois posé ce préambule, la définition d'un patron de fonction ne se distingue de la définition d'une
véritable fonction que par le fait qu'elle peut utiliser le nom de l'argument de patron comme s'il
s'agissait d'un type existant réellement. Ceci signifie que les paramètres, les variables locales et la
valeur renvoyée par la "fonction potentielle" peuvent relever d'un type qui n'est pas connu lors de la
définition de celle-ci.
- un paramètre de type "référence à un objet de type T qui sera précisé le moment venu" ;
- une var_ locale (4) du type T "qui sera précisé le moment venu" ;
- le renvoi d'une valeur du type "pointeur sur un objet de type T qui sera précisé le moment venu"
Exemple :
- Nous désirons écrire une fonction qui va permettre d'interchanger deux valeurs :
On constate que nous sommes en présence du même code, seul le type traité qui diffère.
- Solution: définir un patron pour cette fonction.
« T » est un paramètre qui prend la place d'un type, n'importe quel type (int, double, char etc.).
Cette fonction est vue comme la surdéfinition de fonctions, sauf que nous sommes en présence
d'une seule définition qui va être valable pour tous les types: {« int », « double », « char »
etc.}
C'est le compilateur qui se charge d'instancier le patron de fonction et au besoin ce qui
entraîne moins de risques d'erreurs.
Plusieurs paramètres de type
Tout comme les fonctions, les patrons peuvent disposer de plusieurs arguments. Lorsque c'est le cas, la
"fonction potentielle" dispose de plusieurs "types qui seront précisés le moment venu", ce qui élargit
d'autant ses possibilités quant aux types de ses paramètres, de ses variables locales et de la valeur
qu'elle renvoie. Attention toutefois à ne pas mettre trop de types, sinon la lisibilité et la maintenabilité
vont en pâtir fortement.
Exemple
template <typename T, typename U >
void affiche(T a, U b){…}
Exemple
Type « int »
int x=10, y=20;
« echange(x,y) ».
Les variables x et y sont de type « int ».
Le compilateur instancie « T » à « int » et génère la fonction
« void echange(int&,int&) ».
La fonction appelée est « echange(x,y) ».
Type « double »
double x=10.5, y=20.8;
Les variables x et y sont de type « double ».
Le compilateur instancie « T » à « double » et génère la
fonction « void echange(double&,double&) ».
La fonction appelée est « echange(x,y) ».
Type « char »
char x='a', y='b';
Les variables x et y sont de type « char ».
Le compilateur instancie « T » à « char » et génère la
fonction « void echange(char&,char&) ».
La fonction appelée est « echange(x,y) ».
Surdéfinition de fonctions
En C++, il est possible, au sein d’un même programme, que plusieurs fonctions possèdent le même
nom. Dans ce cas, lorsque le compilateur rencontre l’appel d’une telle fonction, il effectue le choix de
la « bonne » fonction en tenant compte de la nature des arguments effectifs.
int main()
{
int i1 = 5, i2 = 20;
std::cout << "maxS(5, 20) = " << maxS(i1, i2) << std::endl;
// la fonction maxS(int, int) est compilée
return 0;
Surdéfinition de template
les surdéfinitions, vous savez c’est la possibilité d’écrire plusieurs fonctions avec le
même nom mais des arguments différents. Rajoutez désormais à cela la possibilité de
mettre des types paramétrés dans les arguments.
template <typename T, typename U >
void affiche(T a, U b){…}
template <typename T >
void affiche(T a, int i, float f){…}
exemple d’une function template
/ Fichier affiche.h
#include <iostream>
using namespace std;
// ifndef/define/endif sont là pour empêcher
// de multiples inclusions du fichier d'en-tête.
#ifndef T_AFF
#define T_AFF
template <class Z>
void affiche (Z tab[],int taille) {
for (int i=0;i<taille;i++) cout << tab[i] << endl;
}
#endif
// Fichier prgtest.cpp
// Le fichier qui contient la fonction main
#include "affiche.h"
#include <string.h>
using namespace std;
int main() {
double tab1[3] = {1.5,2.4,5.8};
affiche(tab1,3);
char tab2[4] = {'a','b','c','d'};
affiche(tab2,2);
return 0;
}
// Fin fichier prgtest
2 - Patrons de classes
On peut donner des patrons de classes une définition tout à fait analogue à celle que nous avons donné
des patrons de fonctions : un patron de classe est une sorte de "classe potentielle", c'est à dire un
canevas, un plan à partir duquel le compilateur est capable, en fonction des besoins, de générer
plusieurs classes réelles, qui diffèrent par le type des objets qu'elles impliquent.
Le principe de fonctions avec des types paramétrés est étendu aux classes. L’apport des types
paramétrés est crucial pour pallier certains défauts de l’héritage. Par exemple, créer une classe Matrice
qui soit capable de traiter de nombreux types numériques (int, float, truc…). Avec l’héritage, il faut
créer dans un premier temps la classe abstraite Matrice qui définira les opérations sur les matrices, puis
vous implémenterez par héritage les classes MatriceInt et MatriceFloat, MatriceTruc,… Avec les
classes template, vous allez faire de la programmation réellement générique en n’implémentant qu’une
seule classe, Matrice<T> ! C’est évidemment la solution la plus propre et la plus sûre.