Vorteile von Templates

Funktionstemplates werden in den Fällen verwendet, wenn man gleiche Operationen mit Daten verschiedener Typen ausführen muss, zum Beispiel, Suche nach dem maximalen Element in einem Array.  Der wichtigste Vorteil der Templates besteht darin, dass der Entwickler keine Überladung für jeden Typ separat schreiben muss.  Das heißt statt Überladung für jeden Typ zu deklarieren

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

reicht es, ein Funktionstemplate zu schreiben

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

und dieses dann im Code zu verwenden:

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

Hier gibt es einen formellen Parameter T, der den Datentyp setzt, d.h. der Compiler generiert automatisch eine Funktion für jeden Typ – double, datetime und so weiter. Genauso kann man in MQL5 Klassentemplates erstellen und von allen Vorteilen dieser Methode profitieren.

Klassentemplates

Ein Klassentemplate wird mithilfe des Schlüsselwortes template deklariert, nach welchem spitze Klammern stehen <>, in welchen formelle Parameter mit dem Schlüsselwort typename aufgezählt sind. Dies weist den Compiler darauf hin, dass er mit einer allgemeinen Klasse mit dem formellen T Parameter zu tun hat, welcher den realen Typ der Variablen bei der Implementierung der Klasse setzt. Zum Beispiel, erstellen wir eine Vector-Klasse für das Speichern eines Arrays mit Elementen vom Typ T:

#define TOSTR(x) #x+" "   // Makro für die Ausgabe des Objektnamens
//+------------------------------------------------------------------+
//| Vector-Klasse für das Speichern der Elemente vom Typ T           |
//+------------------------------------------------------------------+
template <typename T>
class TArray
  {
protected:
   T                 m_array[];
public:
   //--- der Konstruktor erstellt standardmäßig ein Array für 10 Elemente
   void TArray(void){ArrayResize(m_array,10);}
   //--- Konstruktor für die Erstellung eines Vectors mit der vorgegebenen Arraygröße
   void TArray(int size){ArrayResize(m_array,size);}
   //--- gibt Typ und Anzahl der Daten, die im Objekt vom Typ TArray gespeichert werden
   string Type(void){return(typename(m_array[0])+":"+(string)ArraySize(m_array));};
  };

Weiter erstellen wir auf verschiedene Weisen drei TArray Objekte für das Arbeiten mit verschiedenen Typen

void OnStart()
  {
   TArray<double> double_array;   // standardmäßig beträgt die Veсtor-Größe 10 
   TArray<int> int_array(15);     // Verctor-Größe 15
   TArray<string> *string_array;  // Pointer auf den Vector TArray<string> 
//--- erstellen wir ein dynamisches Objekt
   string_array=new TArray<string>(20);
//--- geben wir den Objektnamen, Datentyp und die Vector-Größe ins Journal aus
   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());
//--- löschen wir das dynamische Objekt vor dem Beenden des Programms 
   delete(string_array);   
  }

Das Ergebnis der Ausführung des Skriptes:

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

Es wurden insgesamt 3 Vectors mit verschiedenen Datentypen erstellt: double, int und string.

Die Klassentemplates passen gut für die Erstellung von Containers - Objekten, die für die Datenkapselung von Objekten jeglichen Typs vorgesehen sind. Die Objekte von Containers stellen Kollektionen dar, die bereits Objekte eines bestimmten Typs beinhalten. In der Regel wird das Arbeiten mit gespeicherten Daten in den Container eingebaut.

Man kann zum Beispiel ein Klassentemplate erstellen, der den Zugriff auf ein Element außerhalb des Arrays verbietet, und auf diese Weise den kritischen Fehler "out of range" vermeiden.

//+------------------------------------------------------------------+
//| Klasse für sicheren Zugriff auf ein Element des Arrays           |
//+------------------------------------------------------------------+
template<typename T>
class TSafeArray
  {
protected:
   T                 m_array[];
public:
   //--- standardmäßiger Konstruktor
   void              TSafeArray(void){}
   //--- Konstruktor für die Erstellung eines Arrays mit vorgegebener Größe
   void              TSafeArray(int size){ArrayResize(m_array,size);}
   //--- Arraygröße 
   int               Size(void){return(ArraySize(m_array));}
   //--- Änderung der Arraygröße 
   int               Resize(int size,int reserve){return(ArrayResize(m_array,size,reserve));}
   //--- den Inhalt des Arrays löschen 
   void              Erase(void){ZeroMemory(m_array);}
   //--- Operator für das Zugreifen auf ein Element des Arrays nach dem Index
   T                 operator[](int index);
   //--- Zuweisungsoperator für das Erhalten aller Elemente aus dem Array
   void              operator=(const T  &array[]); // Array vom Typ T 
  };
//+------------------------------------------------------------------+
//| Operation des Erhaltens eines Elements nach dem Index            |
//+------------------------------------------------------------------+
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("%s index %d is not in range (0-%d)!",__FUNCTION__,index,max);
      return(invalid_value);
     }
//---
   return(m_array[index]);
  }
//+------------------------------------------------------------------+
//| Zuweisungsoperator für das Array                                 |
//+------------------------------------------------------------------+
template<typename T>
void TSafeArray::operator=(const T  &array[])
  {
   int size=ArraySize(array);
   ArrayResize(m_array,size);
//--- der T Typ muss den Copy Operator unterstützen
   for(int i=0;i<size;i++)
      m_array[i]=array[i];
//---
  }
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
   int copied,size=15;  
   MqlRates rates[];
//--- kopieren wir das Array der Kurse
   if((copied=CopyRates(_Symbol,_Period,0,size,rates))!=size)
     {
      PrintFormat("CopyRates(%s,%s,0,%d) Fehlercode %d",
      _Symbol,EnumToString(_Period),size,GetLastError());
      return;
     }
//--- erstellen wir einen Container und fügen ihm das Array der MqlRates Werte hinzu
   TSafeArray<MqlRates> safe_rates;
   safe_rates=rates;
   //--- Index innerhalb des Arrays
   int index=3;
   PrintFormat("Close[%d]=%G",index,safe_rates[index].close);
   //--- Index außerhalb des Arrays
   index=size;
   PrintFormat("Close[%d]=%G",index,safe_rates[index].close);
  }

Bitte beachten Sie, dass bei der Beschreibung von Methoden außerhalb der Deklaration der Klasse auch bei der Deklaration des Templates verwendet werden muss

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

Klassen- und Funktionstemplates erlauben es, mehrere formelle Parameter durch Komma getrennt anzugeben, zum Beispiel, die Map Kollektion für das Speichern von Paaren "Schlüssel – Wert":

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

 

Siehe auch

Funktionstempaltes, Überladung