Métaprogrammation avec des patrons - Définition

Source: Wikipédia sous licence CC-BY-SA 3.0.
La liste des auteurs de cet article est disponible ici.

Contrôle sur la compilation

D'abord, quelques classes:

Exemple  :
       class UneClasseA        {       public:           UneClasseA() {}           virtual ~UneClasseA() {}       };              class UneClasseB : public UneClasseA       {       public:           UneClasseB() {}           virtual ~UneClasseB() {}       };              class UneClasseC : public UneClasseA       {       public:           UneClasseC() {}           virtual ~UneClasseC() {}       };              class UneNouvelleClasseD : public UneClasseA       {       public:           UneNouvelleClasseD() {}           virtual ~UneNouvelleClasseD() {}       };      

Il se peut que, dans un programme, un objet de classe UneClasseC soit manipulé, lors de divers appels de fonctions (ou méthodes), comme un objet de classe UneClasseA afin de profiter des avantages du polymorphisme. Cependant, il se peut que l'on ait un traitement spécifique selon la classe réelle de l'objet. Par exemple, Ceci arrive fréquemment pour l'affichage graphique d'un objet lorsque le programme est découpé selon le concept MVC. Cette technique est un motif de conception appelé visiteur. Classiquement, en C++, cela se fait par une cœrcition descendante (Downcasting en anglais). Il suffit alors de faire les tests:

Exemple  :
       UneClasseB* pConvB = 0;       UneClasseC* pConvC = 0;              if (pConvB = dynamic_cast<UneClasseB*>(pObjetA))       {           // Traitement spécifique à uneClasseB       }       else if (pConvC = dynamic_cast<UneClasseC*>(pObjetA))       {           // Traitement spécifique à uneClasseC       }       else       {           // Erreur, type inconnu       }      

Un problème peut se poser lors de la programmation d'un nouvel objet (UneNouvelleClasseD). En effet, il faut penser à modifier toutes les parties de codes qui font ce genre de traitement. L'erreur ne sera visible que lors de l'exécution du programme, et il faut que cette exécution passe dans cette portion de code pour observer l'erreur.

Ici, la métaprogrammation permet de rendre le programme impossible à compiler si l'on rajoute un type qui n'est traité nulle part.

Pour cela, il faut établir une liste de types qui permettra de vérifier si tous les traitements de chaque type ont été programmés.

Exemple  :
      template <class H, class SousListe> struct ListeType {};             struct ListeFin {};      

une liste de types se présentera sous une forme récursive : une liste contient un type et une sous-liste. La liste se termine par le type ListeFin. Par exemple:

Exemple  :
      ListeType<UneClasseB, ListeType<UneClasseC, ListeFin> >      

Il suffit alors de créer une classe abstraite qui sera spécialisée avec cette liste de types. Cette classe sera rendu non abstraite en implémentant une méthode virtuelle pure pour chaque type (une méthodes par traitement spécifique). Ainsi, l'oubli de l'implémentation de la méthode d'un type de la liste, génèrera une classe abstraite. Une classe abstraite ne pouvant pas être instanciée, le compilateur retournera une erreur lors de la création de l'objet Visiteur. Il suffira alors d'ajouter l'implémentation de la méthode de traitement du nouveau type, pour enlever le comportement abstrait de la classe et permettre une instanciation. Ainsi, le compilateur peut alors compiler le programme et aucune méthode d'implémentation ne sera oubliée.

Il faut donc créer une classe abstraite visiteur avec une liste quelconque de types:

Exemple  :
       template <class liste> class AbsVisiteur;              // Spécialisation de AbsVisiteur pour le cas où c'est une liste de type ListeType       // Hérite de AbsVisiteur de la sous-liste pour créer l'arbre d'héritage afin       // de remonter au parent pour lancer la visite       template <class H, class SousListe>       class AbsVisiteur< ListeType<H, SousListe> > : public AbsVisiteur<SousListe>       {       public:           // Méthode virtuelle pure qui rend la classe abstraite           virtual void visite(H*) = 0;                  template <class Z> void lanceVisite(Z* pZ)           {               H* pConv = 0;                      if (pConv = dynamic_cast<H*>(pZ))               {                   // Le type courant (dans la liste) est celui de la classe courante                   // (dans l'arbre d'héritage)                   visite(pConv);               }               else               {                   // Le type courant (passé à la fonction) est différent de celui du                   // type courant (dans la liste et l'arbre héritage)                   // La sous-liste est testée avec la classe parente                   AbsVisiteur<SousListe>::lanceVisite(pZ);               }           }       };              // Spécialisation pour le type de fin de liste       // C'est la super-classe       class AbsVisiteur< ListeFin >        {       public:                  template <class Z> void lanceVisite(Z* pZ)           {               // Cette classe est la classe mère de toutes les classes correspondant               // au dernier type de la liste (type qui permet de terminer la liste).               // Donc, ici, toute la liste a été testée, et aucune classe correspondante               // au type de la fonction n'a été trouvée.                      // Donc ERREUR le type donné n'est pas dans la liste parcourue               throw "Type introuvable";           }       };      

Ensuite, il faut créer la liste de types spécifiques.

Exemple  :
      typedef ListeType<UneClasseB, ListeType<UneClasseC, ListeFin> > UneListeClasses;      

Enfin, il faut créer la classe qui implémentera tous les traitements spécifiques:

Exemple  :
       class Visiteur : public AbsVisiteur<UneListeClasses>       {       public:           virtual void visite(UneClasseB * b)           {               // Traitement spécifique à uneClasseB           }           virtual void visite(UneClasseC * c)           {               // "Traitement spécifique à uneClasseC           }                  // S'il manque l'implémentation d'une classe de la liste, la classe reste           // abstraite car il reste une méthode virtuelle pure Visit() = 0       };      

Ainsi, pour ajouter un nouveau type, il faut l'ajouter dans la liste de types UneListeClasses, et pour que le programme compile, il faudra ajouter l'implémentation de la méthode visite pour le nouveau type.

Page générée en 0.084 seconde(s) - site hébergé chez Contabo
Ce site fait l'objet d'une déclaration à la CNIL sous le numéro de dossier 1037632
A propos - Informations légales
Version anglaise | Version allemande | Version espagnole | Version portugaise