Ventajas de las plantillas

Las plantillas de funciones se usan en aquellos casos en los que es necesario realizar operaciones iguales con datos de diferente tipo, por ejemplo, la búsqueda del elemento máximo en una matriz.  La primera ventaja en el uso de las plantillas consiste en que el programador no tiene necesidad de escribir una sobrecarga aparte para cada tipo.  Es decir, en lugar de declarar un conjunto de sobrecargas para cada tipo

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

basta con escribir una función de plantilla

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

y después usarla en su código:

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

Aquí, el parámetro formal T, que establece el tipo de datos utilizado, se sustituye al compilar por el tipo utilizado realmente, es decir, el compilador genera automáticamente una función aparte para cada tipo double, datetime, etcétera. Exactamente de la misma forma se pueden crear en el lenguaje MQL5 las plantillas de clases, usando todas las ventajas de este enfoque.

Plantillas de clases

La plantilla de la clase se declara con la ayuda de la palabra clave template, tras la cual van las cuñas <>, dentro de los cuales se enumera la lista de parámetros formales con la palabra clave typename. Esta entrada indicará al compilador que ante él se encuentra una clase generalizada con el parámetro T, que establece el tipo real de la variable al implementar la clase. Por ejemplo, una clase-vector creada para guardar una matriz con elementos del tipo T:

#define TOSTR(x) #x+" "   // macro para mostrar el nombre del objeto
//+------------------------------------------------------------------+
//| Clase-vector del objeto para guardar los elementos del tipo T                       |
//+------------------------------------------------------------------+
template <typename T>
class TArray
  {
protected:
   T                 m_array[];
public:
   //--- el constructor crea por defecto una matriz de 10 elementos
   void TArray(void){ArrayResize(m_array,10);}
   //--- constructor para crear un vector con un número de matriz establecido
   void TArray(int size){ArrayResize(m_array,size);}
   //--- retorna el tipo y la cantidad de datos que se guardan en el objeto del tipo TArray
   string Type(void){return(typename(m_array[0])+":"+(string)ArraySize(m_array));};
  };

A continuación, en el programa creamos de diferentes maneras tres objetos TArray para trabajar con diferentes tipos

void OnStart()
  {
   TArray<double> double_array;   // el vector tiene por defecto un tamaño 10 
   TArray<int> int_array(15);     // el vector tiene un tamaño 15
   TArray<string> *string_array;  // puntero al vector TArray<string> 
//--- creamos un objeto dinámico
   string_array=new TArray<string>(20);
//--- mostramos en el Diario el nombre del objeto, el tipo de datos y el tamaño del vector
   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());
//--- eliminamos el objeto dinámico antes de finalizar el programa
   delete(string_array);   
  }

Resultado de la ejecución del script:

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

Como resultado, se han creado 3 vectores con diferentes tipos de datos: double, int y string.

Las plantillas son adecuadas para desarrollar contenedores, es decir, objetos concebidos para encapsular objetos de cualquier tipo. Los objetos de los contenedores son colecciones que ya contienen objetos de un tipo determinado. Normalmente, en el contenedor se incorpora la implementación del trabajo con los datos que están guardados en el mismo.

Por ejemplo, se puede crear una plantilla de clase que no permita recurrir a un elemento fuera de la matriz, evitando de esta forma el error crítico "out of range".

//+------------------------------------------------------------------+
//| Clase para recurrir de forma segura a un elemento de la matriz               |
//+------------------------------------------------------------------+
template<typename T>
class TSafeArray
  {
protected:
   T                 m_array[];
public:
   //--- constructor por defecto
   void              TSafeArray(void){}
   //--- constructor para crear la matriz del tamaño establecido
   void              TSafeArray(int size){ArrayResize(m_array,size);}
   //--- tamaño de la matriz 
   int               Size(void){return(ArraySize(m_array));}
   //--- cambio de tamaño de la matriz 
   int               Resize(int size,int reserve){return(ArrayResize(m_array,size,reserve));}
   //--- liberación de la matriz 
   void              Erase(void){ZeroMemory(m_array);}
   //--- operador de acceso al elemento de la matriz según el índice
   T                 operator[](int index);
   //--- operador de atribución para obtener todos los elementos de la matriz a la vez
   void              operator=(const T  &array[]); // matriz del tipo T 
  };
//+------------------------------------------------------------------+
//| Operación de obtención de un elemento según el índice                           |
//+------------------------------------------------------------------+
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]);
  }
//+------------------------------------------------------------------+
//| Operación de atribución para la matriz                                |
//+------------------------------------------------------------------+
template<typename T>
void TSafeArray::operator=(const T  &array[])
  {
   int size=ArraySize(array);
   ArrayResize(m_array,size);
//--- el tipo T debe dar soporte al operador de copiado
   for(int i=0;i<size;i++)
      m_array[i]=array[i];
//---
  }
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
   int copied,size=15;  
   MqlRates rates[];
//--- copiamos la matriz de cotizaciones
   if((copied=CopyRates(_Symbol,_Period,0,size,rates))!=size)
     {
      PrintFormat("CopyRates(%s,%s,0,%d) ha retornado el código de error %d",
      _Symbol,EnumToString(_Period),size,GetLastError());
      return;
     }
//--- creamos un contenedor e introducimos en él la matriz de valores MqlRates
   TSafeArray<MqlRates> safe_rates;
   safe_rates=rates;
   //--- índice dentro de los límites de la matriz
   int index=3;
   PrintFormat("Close[%d]=%G",index,safe_rates[index].close);
   //--- índice fuera de los límites de la matriz
   index=size;
   PrintFormat("Close[%d]=%G",index,safe_rates[index].close);
  }

Preste atención a que al describir los métodos fuera de los límites de la declaración de la clase, también es necesario usar la declaración de la plantilla:

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

Las plantillas de las clases y funciones permiten indicar varios parámetros formales separados con una coma, por ejemplo, la colección Map para guardar las parejas "clave — valor":

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

 

Vea también

Plantillas de funciones, Sobrecarga