Modèles

Décembre 2016

Modèles

Pour illustrer simplement les modèles (mot clé template), imaginons que vous ayez besoin de développer un programme de gestion du temps. Pour afficher le calendrier, vous pourriez utiliser un tableau d’objets Date, par exemple, et pour gérer le planning quotidien un tableau d’objets Heure. Les tableaux sont très souvent utilisés en programmation et vous pouvez y stocker aussi bien des types intégrés que des types définis par l’utilisateur. En créant un modèle de classe, vous allez pouvoir définir une seule fois une classe tableau d’objets génériques et l’utiliser chaque fois que vous aurez besoin de stocker des éléments sous forme de tableau en précisant le type d’objet qui y sera stocké. De la même façon, en créant un modèle de fonction d’affichage, vous pourrez utiliser cette dernière pour afficher indifféremment un objet de type date ou un objet de type heure.

Définition et implémentation d’un modèle de classe

Dans cet exemple, nous utilisons une classe tableau qui, en comparaison des tableaux prédéfinis, présente un avantage important : sa taille peut s’adapter de façon dynamique au nombre et à la taille des éléments, vous ne risquez donc pas de générer des erreurs de dépassement de capacité (en clair, écraser la mémoire au-delà de la zone initialement allouée au tableau).

Le code 5.1 (fichier en-tête 05-01.h) contient la déclaration d’une classe simple Date et du modèle de classe Tableau. Dans le code 5.2 (fichier source 05-01.cpp), nous définissons la classe et le modèle que nous utilisons ensuite pour créer un tableau d’objets Date.

Code 5.1 : fichier en-tête 05-01.h

#include<iostream>      //Pour les entrées/sorties  
using namespace std;  
   
class Date   //déclaration de la classe Date  
{  
private:  
   int jour, mois, annee;  
public:  
   Date();              //constructeur par défaut  
   Date(int,int,int);   //Constructeur  
   ~Date();             //Destructeur  
/*** Méthodes d’accès ***/  
 void DefinirDate(int j,int m,int a)  
   { jour=j; mois=m; annee=a; }  
  void LireDate() const   
   { cout<<jour<<" "<<mois<<" "<<annee<<endl; }  
  int LireJour() const { return jour; }  
  int LireMois() const { return mois; }  
  int LireAnnee() const { return annee; }  

  //opérateur = surchargé pour Date  
  Date operator=(const Date);  
};  

//déclaration du modèle avec son type paramètre (T) :  
template <class T>     
class Tableau      //déclaration de la classe "paramètre"  
{  
private:  
   T * p_tab;  //déclaration du pointeur de tableau p_tab  
   //autre élément important d’un tableau, sa taille :  
   int taille;  

public:  
   //Constructeur  
   Tableau(int taille);  
   Tableau(const Tableau &source);  //Constructeur de copie  
   ~Tableau() { delete [] p_tab; }  //Destructeur  

   //surcharge de l’opérateur = :  
   Tableau& operator=(const Tableau&);  

   //surcharges de l’opérateur [] :  
   T& operator[] (int index) { return p_tab[index]; }   
   const T& operator[](int index) const  
    { return p_tab[index]; }  
   //méthode d’accès  
   int LireTaille() const { return taille; }   
};


Code 5.2 : fichier source 05-01.cppxe "modèle:de classe tableau"
1 : #include"05-01.h" //déclarations Date et modèle  
2 :   
3 : int taille_defaut=4;  
4 :   
5 : /***Constructeurs et destructeur de Date***/  
6 :  Date::Date()  
7 :   { jour=0; mois=0; annee=0; }  
8 :  Date::Date(int j,int m,int a)  
9 :   { jour=j; mois=m; annee=a; }  
10:   
11:  Date::~Date() { }  
12:   
13: /****Définition de l’opérateur = surchargé pour Date****/  
14:   
15: Date Date::operator=(const Date source)  
16: {  
17:  if (this == &source) //si les deux objets sont égaux  
18:    return *this;      //on renvoie tout de suite l’objet  
19:     jour = source.LireJour();   //sinon on copie jour  
20:     mois = source.LireMois();   //puis mois  
21:     annee = source.LireAnnee(); //et enfin annee  
22:     return source;  
23: }  
24:/*Définition du constructeur du modèle de   
25:   classe Tableau*/  
26: template <class T>  
27: //initialisation de la donnée membre taille :  
28: Tableau<T>::Tableau(int t=taille_defaut):taille(t)  
29: {  
30:   p_tab = new T[t];  
31: }  
32:  
33: /****Définition du constructeur de copie du modèle****/  
34:  
35: template <class T>  
36: Tableau<T>::Tableau(const Tableau &source)  
37: /*le constructeur de copie reçoit en argument l’adresse   
38:   d’un tableau source protégé par const*/  
39: {  
40: /*on détermine la taille en fonction de celle du tableau  
41:   source :*/  
42:   taille = source.LireTaille();   
43:   p_tab = new T[taille]; //on réserve la mémoire  
44:                          // appropriée dans le tas  
45:    for(int i=0; i<taille; i++) //on recopie les éléments  
46:       p_tab[i]=source[i];  
47: }  
48:   
49: /****Définition de l’opérateur = pour le modèle****/  
50: template <class T>  
51: Tableau<T>& Tableau<T>::operator=(const Tableau &source)  
52: {  
53:   if (this == &source) //si les 2 tableaux sont égaux  
54:   return *this; //on renvoie tout de suite l’objet cible  
55:   // et la suite de l’instruction if n’est pas exécutée  
56:   delete [] p_tab;          //on supprime l’objet cible  
57: /*on détermine sa taille en fonction de celle du tableau  
58:    source :*/  
59:   taille = source.LireTaille(); //  
60: /*on réserve de la mémoire dans le tas pour "recréer"  
61:   l’objet cible : */  
62:   p_tab = new T[taille];          
63: /*on recopie chaque élément de l’objet source dans   
64:    l’objet cible : */  
65:    for (int i=0; i<taille; i++)  
66:       p_tab[i] = source[i];       
67:       return *this;    //on renvoie l’objet cible  
68: }  
69:   
70: int main()  
71: {  
72:   Tableau<Date> Tab_date;  //on crée un tableau de dates  
73:   Date *p_date;    //on déclare un pointeur d’objet Date  
74: /** Les tableaux peuvent être volumineux, c’est pourquoi   
75: il est préférable de les créer dans le tas et de les   
76: manipuler via des pointeurs **/  
77:  
78:cout<<"Saisissez les dates importantes à enregistrer (ex 5 12 2009): "<<endl;  
79: //pour chaque élément du tableau :  
80:   for (int i=0; i<Tab_date.LireTaille(); i++)  
81:   {  
82:     int j, m, a;  
83:     cout << "ttDate "<<"i" <<": ";  
84:     cin >> j;           //on récupère la valeur  
85:     cin >> m;            //saisie par l’utilisateur  
86:     cin >> a;  
87:     p_date = new Date(j, m, a); //on réserve la mémoire   
88:                                 //pour le nouvel élément  
89:     Tab_date[i] = *p_date; //on stocke sa valeur à   
90:                            // l’emplacement réservé  
91:   }  
92:      
93:  cout << "Voici le tableau des dates enregistrées: n" ;  
94:    for (int k=0; k<Tab_date.LireTaille(); k++)  
95:    {  
96:       Tab_date[k].LireDate() ;  
97:    }  
98: }


L’exécution de ce programme donne le résultat suivant :

Saisissez les dates importantes à enregistrer (ex 5 12 2009):   
                Date 1 : 1 1 2010  
                Date 2 : 2 2 2010  
                Date 3 : 3 3 2010  
                Date 4 : 4 4 2010  
Voici le tableau des dates enregistrées:   
1 1 2010  
2 2 2010  
3 3 2010  
4 4 2010

À savoir

En C++, le nom donné à unxe "pointeur:de tableau" tableau est un pointeur constant sur le premier élément de ce dernier. Cela signifie que tab_date est équivalent à &tab_date[0]. La numérotation des éléments s’effectue comme en C : en partant de zéro.

Quelques remarques sont nécessaires à propos de la syntaxe de ce programme.
  • Ligne 28 : jusqu’à présent, nous avions initialisé les données membres des objets en leur affectant une valeur dans le corps du constructeur. Vous pouvez aussi le faire sur la première ligne de définition de ce constructeur en appliquant la syntaxe classe::classe():var1(valeur1),var2(valeur2),etc. {}.
  • Remarquez les nombreuses similitudes entre le constructeur de copie du modèle et la définition de l’opérateur = surchargé. Les raisons sont évidentes.
  • Ligne 52 : this est le pointeur sur les données de l’objet, *this représente donc l’objet lui-même .

Définition et implémentation d’un modèle de fonction

Un modèle de fonction est une fonction qui peut être utilisée avec divers types d’objets. Pour illustrer ce concept, nous allons ajouter à notre exemple précédent une classe Heure et une fonction modèle Affiche() qui nous permettra d’afficher l’un ou l’autre type d’objet.

Code 5.3 : déclaration et définition des classes Date et Heure (fichier 05-03.h)
#include<iostream>      //Pour les entrées/sorties  
using namespace std;  

class Heure; //Déclaration de la classe Heure  

class Date   //déclaration et définition de la classe Date  
{  
private:  
   int jour, mois, annee;  
public:  
   Date();              //constructeur par défaut  
   Date(int,int,int);         //Constructeur  
   ~Date();                   //Destructeur  

   //méthodes d’accès  
   void DefinirDate(int j,int m,int a)  
     { jour=j; mois=m; annee=a; }  
   int LireJour() const { return jour; }  
   int LireMois() const { return mois; }  
   int LireAnnee() const { return annee; }  

  //opérateur = surchargé pour Date  
  Date operator=(const Date);  

   //opérateur ami (voir section suivante):  
   friend ostream& operator<< (const Date&, const Heure&);  
};  

class Heure   //définition de la nouvelle classe Heure  
{  
private:  
   int heure, minute;  
public:  
   Heure(int,int);            //Constructeur  
   ~Heure();                  //Destructeur  

   //méthodes d’accès  
   void DefinirHeure(int h,int m){ heure=h; minute=m; }  
   int LireHeure() const { return heure; }  
   int LireMinute() const { return minute; }  

   //opérateur ami (voir section suivante):  
   friend ostream& operator<< (const Date&, const Heure&);  
};

Code 5.4 : utilisation d’un modèle de fonction (fichier 05-04.cpp)

#include"05-03.h" //Inclusion des déclarations Date et Heure  

/****Constructeurs et destructeur de Date****/  
Date::Date()  
  { jour=0; mois=0; annee=0; }  
Date::Date(int j,int m,int a)  
{ jour=j; mois=m; annee=a; }  

Date::~Date() { }  

/****Constructeur et destructeur de Heure****/  
Heure::Heure(int h,int m)  
{ heure=h; minute=m;  }  

Heure::~Heure() { }  

/****opérateur << surchargé pour afficher une date ****/  
ostream& operator<< (ostream& sortie, const Date une_date)   
{   
   sortie << une_date.LireJour() << " " << une_date.LireMois() << " " << une_date.LireAnnee();   
   return sortie;   
}   

/****opérateur << surchargé pour afficher une heure ****/  
ostream& operator<< (ostream& sortie, const Heure une_heure)   
{   
   sortie << une_heure.LireHeure() << "h" << une_heure.LireMinute();  
   sortie << "mn";   
   return sortie;   
}   

//déclaration du modèle avec son type paramètre (T) :   
template <class T>   
//déclaration de la fonction "paramétrée" :   
void Affiche(T x)   
{   
   cout << x << endl; //on affiche l’objet à condition que    
}          //ce dernier implémente l’opérateur surchargé <<   

int main()  
{  
  cout << "nNous créons l’objet Date_initiale = 1 1 2009 ";  
   Date Date_initiale(1,1,2009);  
   cout << "nPuis l’objet Heure_initiale = 12h30 " << endl;  
   Heure Heure_initiale(12,30);  

   cout << "nNous affichons maintenant ces deux objets avec Affiche()n" << endl;  
   cout << "Date:t";  
   Affiche(Date_initiale);  
   cout << "Heure:t";  
   Affiche(Heure_initiale);  
}


L’exécution de ce programme produit l’affichage suivant :

Nous créons l’objet Date_initiale = 1 1 2009  
Puis l’objet Heure_initiale = 12h30  

Nous affichons maintenant ces deux objets avec Affiche()  

Date:   1 1 2009  
Heure:  12h30mn


Vous pouvez constater que la syntaxe de déclaration d’une fonction modèle est assez simple. N’oubliez pas, cependant, que tout opérateur apparaissant dans le corps de cette fonction doit être implémenté pour tous les types d’objet manipulés (comme l’opérateur << dans notre exemple).



Le texte original de cette fiche pratique est extrait de
«Tout sur le C++» (Christine EBERHARDT, Collection
CommentCaMarche.net, Dunod, 2009)

A voir également :

Ce document intitulé «  Modèles  » issu de CommentCaMarche (www.commentcamarche.net) est mis à disposition sous les termes de la licence Creative Commons. Vous pouvez copier, modifier des copies de cette page, dans les conditions fixées par la licence, tant que cette note apparaît clairement.