Avantages des templates

Les templates de fonctions sont utilisés lorsque vous avez besoin d'effectuer des opérations identiques sur différents types de données, par exemple, chercher l'élément maximum dans un tableau.  Le principal avantage d'appliquer des templates est que vous n'avez pas à coder une surcharge séparée pour chaque type.  Au lieu de déclarer plusieurs surcharges pour chaque type

double ArrayMax(double array[])
  {
   ...
  }
int ArrayMax(int array[])
  {
   ...
  }
uint ArrayMax(uint array[])
  {
   ...
  }
long ArrayMax(long array[])
  {
   ...
  }
datetime ArrayMax(datetime array[])
  {
   ...
  }

nous n'avons qu'à écrire une seule fonction templatée

template<typename T> 
T ArrayMax(T array[])
  {
   if(ArraySize()==0) 
      return(0);
   uint max_index=ArrayMaximum(array);  
   return(array[max_index]);
  }

pour l'utiliser dans votre code :

double high[];
datetime time[];
....
double max_high=ArrayMax(high);
datetime lasttime=ArrayMax(time);

Ici, le paramètre formel T spécifiant le type de la donnée utilisée est remplacé avec le type effectivement utilisé lors de la compilation, c'est à dire que le compilateur génére automatiquement une fonction séparée pour chaque type — double, datetime, etc. MQL5 vous permet aussi de développer des templates de classes utilisant tous les avantages de cette approche.

Templates de classe

Un template de classe est déclaré en utilisant le mot-clé template suivi par des chevrons <> donnant la liste de paramètres formels avec le mot-clé typename. Cette entrée informe la compilateur qu'il traite une classe générique avec le paramètre formel T définissant un type réel de variable lors de l'implémentation d'une classe. Par exemple, créons une classe vector pour stocker un tableau d'éléments de type T :

#define TOSTR(x) #x+" "   // macro affichant le nom d'un objet
//+------------------------------------------------------------------+
//| Classe Vector pour stocker des éléments de type T                |
//+------------------------------------------------------------------+
template <typename T>
class TArray
  {
protected:
   T                 m_array[];
public:
   //--- constructeur créant un tableau de 10 éléments par défaut
   void TArray(void){ArrayResize(m_array,10);}
   //--- constructeur créant un tableau ayant la taille spécifiée
   void TArray(int size){ArrayResize(m_array,size);}
   //--- retourne le type et la quantité de données stockées dans l'objet de type TArray
   string Type(void){return(typename(m_array[0])+":"+(string)ArraySize(m_array));};
  };

Appliquons maintenant différentes méthodes pour créer trois objets TArray dans le programme pour travailler avec différents types

void OnStart()
  {
   TArray<double> double_array;   // le vector a une taille par défaut de 10 
   TArray<int> int_array(15);     // le vector a une taille de 15
   TArray<string> *string_array;  // pointeur vers un vecteur de TArray<string>
//--- crée un objet dynamique
   string_array=new TArray<string>(20);
//--- affiche le nom d'un objet, le type de donnée et la taille du vecteur dans le Journal
   PrintFormat("%s (%s)",TOSTR(double_array),double_array.Type());
   PrintFormat("%s (%s)",TOSTR(int_array),int_array.Type());
   PrintFormat("%s (%s)",TOSTR(string_array),string_array.Type());
//--- supprime un objet dynamique avant de terminer le programme
   delete(string_array);   
  }

Résultats de l'exécution du script :

  double_array  (double:10)
  int_array  (int:15)
  string_array  (string:20)

Nous avons maintenant 3 vecteurs avec des types de données différents : double, int et string.

Les templates de classe sont pratiques pour développer des conteneurs — des objets conçus pour encapsuler d'autres objets de n'importe quel type. Les objets conteneurs sont des collections contenant déjà des objets d'un certain type. Habituellement, travailler avec les données stockées est intégré instantanément au conteneur.

Par exemple, vous pouvez créer un template de classe qui ne permet pas d'accéder à un élément en dehors du tableau, évitant ainsi l'erreur critique "out of range".

//+------------------------------------------------------------------+
//| Classe laissant un accès libre à un élément du tableau           |
//+------------------------------------------------------------------+
template<typename T>
class TSafeArray
  {
protected:
   T                 m_array[];
public:
   //--- constructeur par défaut
   void              TSafeArray(void){}
   //--- constructeur pour créer le tableau avec la taille spécifié
   void              TSafeArray(int size){ArrayResize(m_array,size);}
   //--- taille du tableau
   int               Size(void){return(ArraySize(m_array));}
   //--- change la taille du tableau
   int               Resize(int size,int reserve){return(ArrayResize(m_array,size,reserve));}
   //--- libère le tableau
   void              Erase(void){ZeroMemory(m_array);}
   //--- opérateur pour accéder à un élément du tableau par son indice
   T                 operator[](int index);
   //--- opérateur d'assignement pour récupérer tous les éléments du tableau en une seule fois
   void              operator=(const T  &array[]); // T type array 
  };
//+------------------------------------------------------------------+
//| Récupération d'un élément par son indice                         |
//+------------------------------------------------------------------+
template<typename T>
T TSafeArray::operator[](int index)
  {
   static T invalid_value;
//---
   int max=ArraySize(m_array)-1;
   if(index<0 || index>=ArraySize(m_array))
     {
      PrintFormat("L'indice %d de %s n'est pas dans l'intervalle (0-%d) !",index, __FUNCTION__,max);
      return(invalid_value);
     }
//---
   return(m_array[index]);
  }
//+------------------------------------------------------------------+
//| Assignement du tableau                                           |
//+------------------------------------------------------------------+
template<typename T>
void TSafeArray::operator=(const T  &array[])
  {
   int size=ArraySize(array);
   ArrayResize(m_array,size);
//--- Le type T doit supporter l'opérateur de copie
   for(int i=0;i<size;i++)
      m_array[i]=array[i];
//---
  }
//+------------------------------------------------------------------+
//| Fonction de lancement du script                                  |
//+------------------------------------------------------------------+
void OnStart()
  {
   int copied,size=15;  
   MqlRates rates[];
//--- copy le tableau des cotations
   if((copied=CopyRates(_Symbol,_Period,0,size,rates))!=size)
     {
      PrintFormat("CopyRates(%s,%s,0,%d) a retourné le code d'erreur %d",
      _Symbol,EnumToString(_Period),size,GetLastError());
      return;
     }
//--- crée un conteneur et y insère le tableau de valeurs MqlRates
   TSafeArray<MqlRates> safe_rates;
   safe_rates=rates;
   //--- indice dans le tableau
   int index=3;
   PrintFormat("Close[%d]=%G",index,safe_rates[index].close);
   //--- indice en dehors du tableau
   index=size;
   PrintFormat("Close[%d]=%G",index,safe_rates[index].close);
  }

Veuillez noter que la déclaration du template doit également être utilisée lors de l'implémentation des méthodes en dehors de la déclaration de la classe :

template<typename T>
T TSafeArray::operator[](int index)
  {
   ...
  }
template<typename T>
void TSafeArray::operator=(const T  &array[])
  {
   ...
  }

Les templates de classe et de fonction vous permettent de définir plusieurs paramètres formels séparés par une virgule, par exemple, la collection Map pour stocker des paires "clé — valeur" :

template<typename Key, template Value>
class TMap
  {
   ...
  }

Voir aussi

Templates de fonction, Surcharge