Cours C++.livre(Templates)
14 pages
Catalan

Cours C++.livre(Templates)

-

Le téléchargement nécessite un accès à la bibliothèque YouScribe
Tout savoir sur nos offres
14 pages
Catalan
Le téléchargement nécessite un accès à la bibliothèque YouScribe
Tout savoir sur nos offres

Description

CHAPITRE 16 Templates (Modèles, Patrons, Classes paramétrables)Le langage C++ 255einev Télécommunications mjn 16.1 UtilisationLes modèles sont un outil très puissant de C++, qui ont été introduits relativement tard,avec la version Cfront 3.0 du compilateur. L'utilité des modèles (appelés parfois "patron" parcertains auteurs, ou «classes paramétrables» par d'autres, utilisez "template" pour être sursd'être compris), est mise en doute par certains auteurs, qui estiment que cette construction n'estpas vraiment utile. Ce n'est pas l'avis de l'auteur de ce cours.Un modèle permet de définir une fonction ou une classe générique, et de laisser le com-pilateur effectuer les ajouts nécessaires lors de l'implémentation définitive. Un modèle est dé-fini par le mot-clé template suivi d’une liste de paramètres formels qui constitue les argumentsdu template. La liste de paramètres formels est délimitée par une paire de signes d’inégalité(«<>»), et les paramètres individuels sont séparés par des virgules. La liste de paramètres for-mels ne doit en aucun cas être vide.256 Le langage C++einev Télécommunications mjn 16.2 Fonctions templateUne fonction template définit une procédure ou une fonction sous forme de template.La liste de paramètres formels est constituée d’une suite d’expressions constituées du mot ré-servé class suivi d’un identificateur. Ainsi, les définitions suivantes constituent des défini-tions correctes de fonction template (fonction retournant la ...

Informations

Publié par
Nombre de lectures 104
Langue Catalan

Extrait

CHAPITRE 16
Le langag
Templates (Modèles, Patrons, Classes paramétrables)
e C++
255
einev
16.1 Utilisation
Télécommunications
mjn
Les modèles sont un outil très puissant de C++, qui ont été introduits relativement tard, avec la version Cfront 3.0 du compilateur. L'utilité des modèles (appelés parfois "patron" par certains auteurs, ou «classes paramétrables» par d'autres, utilisez " template " pour être surs d'être compris), est mise en doute par certains auteurs, qui estiment que cette construction n'est pas vraiment utile. Ce n'est pas l'avis de l'auteur de ce cours.
Un modèle permet de définir une fonction ou une classe générique, et de laisser le com-pilateur effectuer les ajouts nécessaires lors de l'implémentation définitive. Un modèle est dé-fini par le mot-clé template suivi d’une liste de paramètres formels qui constitue les arguments du template. La liste de paramètres formels est délimitée par une paire de signes d’inégalité («<>»), et les paramètres individuels sont séparés par des virgules. La liste de paramètres for-mels ne doit en aucun cas être vide.
256
Le langage C++
einev
16.2 Fonctions template
Télécommunications
mjn
Une fonction template définit une procédure ou une fonction sous forme de template. La liste de paramètres formels est constituée d’une suite d’expressions constituées du mot ré-servé class suivi d’un identificateur. Ainsi, les définitions suivantes constituent des défini-tions correctes de fonction template (fonction retournant la valeur minimale ou maximale de deux quantités de type donné) :
template <class Type> Type min(Type, Type); template <class Type> Type max(Type, Type); Lidentificateur Type remplace n’importe quelle déclaration de type. La liste de para-mètres formels indique au compilateur que lors de l’instanciation du template, il devra rem-placer l’dentificateur Type par le type d’instanciation. La séquence d’instructions suivante montre un exemple d’utilisation des templates min et max définis çi-dessus: #include <iostream.h> template <class Type> Type min(Type t1, Type t2) { return t1 < t2 ? t1 : t2; }
template <class Type> Type max(Type t1, Type t2) { return t1 > t2 ? t1 : t2; } void main() { int x = 3; int y = 4; cout<<"Min <int> ( 3, 4) "<<min(x, y)<<endl; cout<<"Max <int> ( 3, 4) "<<max(x, y)<<endl; double xx = 3.5; double yy = 4.5; cout<<"Min <double> ( 3.5, 4.5 ) "<<min(xx, yy)<<endl; cout<<"Max <double> ( 3.5, 4.5 ) "<<max(xx, yy)<<endl; }
Il est possible de surcharger un template, au même titre qu’une fonction normale, la seu-le condition étant que la signature de la fonction diffère. Le segment de code suivant constitue une implémentation correcte d’une fonction template retournant la valeur minimale d’un ta-
Le langage C++
257
einev
Télécommunications
mjn
bleau de valeurs de type indéterminé (Type), et de dimensions données comme argument de la fonction. Ce template peut être utilisé sans problème en conjonction avec l’exemple donné précedemment.
template <class Type> Type min(Type* array, unsigned size) { Type minimumValue = array[0]; for (int i = 1; i < size; i++) if (array[i] < minimumValue) minimumValue = array[i]; return minimumValue; }
Les lignes suivantes indiquent la manière correcte d’instancier le template défini ci-des-sus, avec un tableau d’entiers prédéfini :
258
int ia[] = { 0, 5, 10, 2, 4, 6, 18, 9, 3 }; cout<<" Min <int*> "<<min(ia, (sizeof(ia) / sizeof(int)))<<endl;
Le langage C++
einev
16.3 Classes template
Télécommunications
mjn
On a précedemment implémenté dans le cadre d’un exercice (“(D) Tableau de dimen-sions variables.”, page351) une classe tableau de dimensions variables, qui permettait de dé -finir des instances de tableaux d’entiers de dimensions variables. Rappelons brièvement une définition possible de ce tableau ci-dessous (sans commentaires) : class IntArray  {  private :  long low;  long high;  long min;  long max;  unsigned bump;  int *arr;  int *aptr;  void init(long length, long start, unsigned bumpsize);  void chsize(long newBottom, long newTop);  public:  IntArray(long l, long s = 0, unsigned b = 1024);  IntArray();  IntArray(const IntArray &t);  ~IntArray();  IntArray &operator=(const IntArray &);  long size() const;  void reset(long len = 0, long st = 0);  long smallest() const;  long largest() const;  int &operator[](long e);  int &end();  };
Plus tard, on remarque que l’on aurait également besoin d’un tableau de nombres réels, ayant un comportement identique au tableau d’entiers, mais contenant des nombres réels en lieu et place d’entiers. La solution évidente est de recopier le code source dans un autre fi-chier, et de remplacer judicieusement le type int par le type float . On constatera, ce faisant, que le nombre de modifications à apporter est remarquable-ment faible. Au lieu d’appeler notre classe IntArray, nous l’appelerons FloatArray, et nous remplacerons partout où cela est nécessaire int par float , pour obtenir le résulat suivant (les modifications sont indiquées en caractères gras) : class FloatArray  {  private :  long low;  long high;
Le langage C++
259
einev
Télécommunications
 long min;  long max;  unsigned bump;         float  *arr;         float  *aptr;  void init(long length, long start, unsigned bumpsize);  void chsize(long newBottom, long newTop);  public:         FloatArray (long l, long s = 0, unsigned b = 1024);         FloatArray ();         FloatArray (const FloatArray &t);  ~ FloatArray ();         FloatArray  &operator=(const FloatArray &);  long size() const;  void reset(long len = 0, long st = 0);  long smallest() const;  long largest() const;         float  &operator[](long e);         float  &end();  };
mjn
Un peu plus tard, on s’apercevra que l’on désire aussi implémenter un tableau de lon-gueur variable contenant des coordonnées cartésiennes (couples de valeurs réelles), etc... L’in-convénient de cette manière de faire est évident : on multiplie le nombre de fichiers et de lignes de code contenant essentiellement le même algorithme, ce qui devient vite difficile à maintenir. De plus, si un programmeur apporte une modification à IntArray (correction ou amélioration), cette modification n’est pas répercutée dans FloatArray , ce qui est regret-table. Enfin, pour chaque type, on a défini un tableau particulier dont le nom est différent, bien que sa fonctionnalité soit la même, ce qui nuit à la clarté du code. La méthode permettant de tourner cette difficulté en C++ est la définition de classes template 1 . La définition d’une classe template peut sembler au premier abord complexe et in-habituelle. Comme pour la définition de fonctions template, la définition de classes template commence par le mot réservé template suivi d’une liste de paramètres formels. La liste de pa-ramètres formels est délimitée par des signes d’inégalité («<», «>»), et les éléments de la liste sont séparés par des virgules. L’utilisation du mot réservé class dans la liste de paramètres indique que l’identificateur correspondant désigne un type. Voici la déclaration d’un template Array : template <class TYPE> class Array; Une classe template peut aussi déclarer en paramètre une expression. template <class TYPE, int BufferSize> class Buffer;
1. Il est également possible de résoudre ce problème par l’utilisation de macros; cependant, rappelons que les macros sont traitées par le préprocesseur et non par le compilateur. Il s’agit donc d’une simple substitution d’écriture. L’expansion de la macro s’effectue forcément sur une ligne, ce qui la rend difficile à envoyer par courrier électronique, par exemple. Enfin, il n’est pas sans autre possible de définir des bibliothèques de macros. 260 Le langage C++
einev
Télécommunications
mjn
Par comparaison, voici la définition de la classe Array définie sous forme de template. Cette définition est à rapprocher de la définition de IntArray rappelée ci-dessus. Les différen-ces significatives sont indiquées en caractères gras :
template <class TYPE> class Array  {  private :  long low;  long high;  long min;  long max;  unsigned bump;         TYPE  *arr;         TYPE  *aptr;  void init(long length, long start, unsigned bumpsize);  void chsize(long newBottom, long newTop);  public:         Array (long l, long s = 0, unsigned b = 1024);         Array ();         Array (const Array &t);         Array (); ~         Array  &operator=(const Array &);  long size() const;  void reset(long len = 0, long st = 0);  long smallest() const;  long largest() const;         TYPE &operator[](long e);         TYPE &end();  };
La liste de paramètres fait partie de l’identification du tableau. Le nom du type template n’est par Array , mais bien Array<TYPE>. Ce qui est logique, mais conduit à une nota-tion très lourde, en particulier lors de l’implémentation du template; le contexte est en fait Array<TYPE> , et non Array, si bien que pour implémenter un membre en dehors de la dé-claration de classe (de manière non implicite, comme on a coutume de la faire dans un fichier implémentation .C séparé du fichier définition .h), il faudra chaque fois rappeler au compila-teur qu’il s’agit du contexte Array<TYPE> . De plus, pour satisfaire les besoins syntactiques du compilateur, chaque implémentation de membre requiert l’en tête template <class TYPE>. La définition çi-dessus pourrait faire partie d’un fichier en-tête (Array.h). L’implémen-tation correspondante pourrait, de manière similaire aux classes vues jusqu’ici, faire l’objet d’un fichier Array.C, dont voici un exemple de définition, et qui illustre la relative lourdeur de syntaxe des template : #include"Array.h"  //  // Implementation : Array.C  //
Le langage C++
261
einev
Télécommunications
template <class TYPE> Array<TYPE>::Array() { init(0, 0, 1024); } template <class TYPE> Array<TYPE>::Array(long l, long s, unsigned b) { init(l, s, b); } template <class TYPE> Array<TYPE>::Array(const Array& t)  {  init(t.high - t.low + 1, t.low, t.bump);  // Remarquer l'utilisation de l'opérateur d'assignation...  this = t; *  } template <class TYPE> Array<TYPE>::~Array()  {  delete[] arr;  }  template <class TYPE> void Array<TYPE>::init(long len, long start, unsigned bmp)  {  if (len < 0)  len = 0;  high = start + len - 1;  low = start;  max = high;  min = low;  bump = bmp > 0 ? bmp : 1024;  if (max < min)  max = min;  arr = new int [max - min + 1];  for (int i = 0; i < max - min + 1; i++)  arr[i] = 0;      aptr = arr - min;  } template <class TYPE> void Array<TYPE>::chsize(long lelt, long helt)  {  long nlow = lelt < low ? lelt : low;  long nhigh = helt > high ? helt : high;  if (nlow <= min || nhigh >= max)    {  long nmin = nlow < min ? nlow : min;  long nmax = nhigh > max ? nhigh : max;  long m = max - min + 1;  long nm = nmax - nmin + 1 + (m > bump ? bump : m); 262 Le langage C++
mjn
einev
Télécommunications
 long nl = nhigh - nlow + 1;  int *narr = new int [nm];  int *naptr = narr - nmin;          for (long i = 0; i < nm; i++) narr[i] = 0;   for (i = low; i <= high; i++) naptr[i] = aptr[i];  delete[] arr;  arr = narr;  aptr = naptr;  min = nmin;  max = nmax;  }  high = nhigh;  low = nlow;  } template <class TYPE> void Array<TYPE>::reset(long len, long st)  {  chsize(st, st + len - 1);  high = st + len - 1;  low = st;  } template <class TYPE> long Array<TYPE>::size() const { return high - low + 1; } template <class TYPE> long Array<TYPE>::smallest() const { return low; } template <class TYPE> long Array<TYPE>::largest() const { return high; } template <class TYPE> int& Array<TYPE>::end() { return (*this)[largest() + 1]; } template <class TYPE> Array<TYPE> &Array<TYPE>::operator=(const Array<TYPE> &a)  {  if (&a == this)    return *this;  if (a.low < low || a.high > high)  chsize(a.low, a.high);  low = a.low;  high = a.high;  for (long i = a.low; i <= a.high; i++)  aptr[i] = a.aptr[i];  bump = a.bump;  return *this;  } template <class TYPE> Le langage C++
mjn
263
einev
Télécommunications
TYPE& Array<TYPE>::operator[](long e)  {  if (e <= low || e >= high) chsize(e,e);  return aptr[e];  }
mjn
Pour utiliser ce template dans un programme, on pourrait par exemple écrire le program-me principal suivant :  //  // Programme de test.  // #include <iostream.h> #include <stdlib.h>// Pour abort() #include "Array.C" _ _ #include <new.h> // Pour set new handler() void noMoreMemory()  {  cerr<<"Free store exhausted ! "<<endl;  abort();  } int main()  {  set new handler(noMoreMemory); _ _  Array<int> ia(10), ib(20, -10); // Instanciation, TYPE == int  for (int i = 0; i < 10; i++) ia[i] = i;    for (i = 0; i < 25; i++) ib[i] = 100 + i;   cout<<ia[5]<<" "<<ib[5]<<endl;  ia = ib;  cout<<ia[5]<<" "<<ib[5]<<endl;   return 0;  }
On peut remarquer que, au lieu d’importer Array.h, on a importé ici Array.C. Il ne s’agit pas d’une erreur, encore que certains systèmes permettent de n’importer que le fichier Ar-ray.h. Pour générer une instance particulière d’un template, le compilateur C++ est en effet obligé de recréer, sur la base de la définition du template, une implémentation particulière cor-respondant à la liste de paramètres formels fournie. Pour cette création, il a besoin non seule-ment de la définition, mais également de l’implémentation de la classe. Il est donc nécessaire de lui fournir le code source de l’implémentation avec celui de la définition. Beaucoup d’auteurs résolvent ce problème en incluant l’implémentation dans le fichier .h, en lieu et place du fichier .C, ce qui permet à l’utilisateur de n’importer que des fichiers avec l’extension .h. D’un autre côté, lorsque l’on établit la documentation d’un module, il est généralement peu judicieux de mélanger définition et implémentation, si bien que la généra-
264
Le langage C++
einev
Télécommunications
mjn
tion de deux fichiers séparés se justifie néanmoins 1 . Le fait que le compilateur doive regénérer une implémentation nouvelle pour chaque instanciation avec une liste de paramètres formels différente implique que cette implémenta-tion doive être compilée. De fait, un template est toujours compilé deux fois : lorsque l’on définit le template, la compilation consiste surtout en une vérification syntactique : aucun code n’est généré; lors de l’instanciation, du code est généré, et il peut arriver que du code ayant passé correctement la première étape de compilation révèle des erreurs lors de la deuxième compilation.
16.3.1 Paramètres formels d’expression constante On a mentionné que, hors les types indiqués par le mot réservé class , il était égale-ment possible d’utiliser des paramètres formels de type expression constante dans la liste de paramètres du template. Par exemple, une classe template décrivant un écran graphique pos-sède, entre autres caractéristiques remarquables, ses résolutions verticales et horizontales. Pour un type d’écran donné, ces valeurs sont constantes. A chaque coordonnée d’écran est associé un entier décrivant le pixel considéré. On peut définir une classe Screen (Ecran) template de la manière suivante : template <int vRes, int hRes> class Screen { .... unsigned int screenBuffer[vRes][hRes]; .... };
Un écran de type particulier sera instancié par : Screen<480, 640> myScreen; Il est parfois avantageux, du point de vue de la lisibilité du programme résultant, d’as-socier la construction typedef à l’instanciation d’un template. Ainsi, pour le type d’écran ins-tancié çi-dessus : typedef Screen<480, 640> StandardVGAScreen; typedef Screen<768, 1024> HiResScreen; StandardVGAScreen myScreen, hisScreen; HiResScreen paoScreen, cadScreen; Ceci permet également de s’affranchir de l’écriture un peu lourde des templates. Les expressions tenant lieu de paramètres doivent pouvoir être évalués au moment de
1. Les systèmes de modélisation modernes (comme Rational Rose, ou encore Together) imposent le plus souvent la sépa-ration entre définition et implémentation, ce qui est une excellente habitude.
Le langage C++
265
  • Univers Univers
  • Ebooks Ebooks
  • Livres audio Livres audio
  • Presse Presse
  • Podcasts Podcasts
  • BD BD
  • Documents Documents