Qual é a melhor coisa sobre os modelos?

Os modelos das funções são usados quando é necessário realizar as mesmas operações com diferentes tipos de dados, por exemplo, localizar o elemento máximo na matriz. A principal vantagem da utilização de modelos é que o programador não necessita escrever uma sobrecarga separada para cada tipo. Ou seja, em vez de várias declarações do 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 escrever uma função de modelo

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

e, em seguida, usá-la em seu código:

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

Neste caso, o parâmetro formal T especifica o tipo de dados utilizados, durante a compilação, ele é substituído pelo tipo real utilizado, ou seja, o compilador gera automaticamente uma função separada para cada tipo, isto é, double, datetime e assim por diante. Da mesma forma, na linguagem MQL5, você pode criar modelos de classes usando todas as vantagens de tal abordagem.

Modelos de classes

O modelo de classe é declarado usando a palavra-chave template, seguida pelos colchetes angulares <>, nos quais são listados os parâmetros formais com a palavra-chave typename. Este registro indica ao compilador que está perante uma classe genérica na com o parâmetro formal T que especifica o tipo real da variável ao implementar a classe. Por exemplo, criamos uma classe vector para armazenar a matriz com elementos do tipo T:

#define TOSTR(x) #x+" "   // macro para exibir o nome do objeto 
//+------------------------------------------------------------------+
//| Classe vector para armazenar elementos do tipo T                       |
//+------------------------------------------------------------------+
template <typename T>
class TArray
  {
protected:
   T                 m_array[];
public:
   //--- por padrão, o construtor cria uma matriz de 10 elementos
   void TArray(void){ArrayResize(m_array,10);}
   //--- construtor para criar um vetor com o tamanho definido da matriz
   void TArray(int size){ArrayResize(m_array,size);}
   //--- retorna o tipo e número de dados que são armazenados no objeto do tipo TArray
   string Type(void){return(typename(m_array[0])+":"+(string)ArraySize(m_array));};
  };

Em seguida, no programa, criamos de maneiras diferentes três objetos TArray para trabalhar com diferentes tipos

void OnStart()
  {
   TArray<double> double_array;   // por padrão, o tamanho do vetor é 10 
   TArray<int> int_array(15);     // o tamanho do vetor é 15
   TArray<string> *string_array;  // ponteiro para o vetor TArray<string> 
//--- criamos o objeto dinâmico
   string_array=new TArray<string>(20);
//--- no Diário, exibimos o nome do objeto, tipo de dados e tamanho do vetor
   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());
//--- excluímos o objeto dinâmico antes de encerrar o programa
   delete(string_array);   
  }

Resultado do script:

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

Como resultado, foram criados 3 vetores com diferentes tipos de dados: double, int e string.

Os modelos de classes são adequados para desenvolver recipientes, isto é, os objetos destinados a encapsular qualquer tipo de objeto. Os objetos dos recipientes são coleções que já contêm objetos de um tipo particular. Normalmente, o recipiente imediatamente é integrado e implementado para trabalhar com dados que são armazenados nele.

Por exemplo, é possível criar um modelo de classe que não permita acessar um elemento fora da matriz e, assim, evitar o erro crítico "out of range".

//+------------------------------------------------------------------+
// | Classe para acessar com segurança um elemento da matriz              |
//+------------------------------------------------------------------+
template<typename T>
class TSafeArray
  {
protected:
   T                 m_array[];
public:
   //--- construtor por padrão
   void              TSafeArray(void){}
   //--- construtor para criar a matriz do tamanho especificado
   void              TSafeArray(int size){ArrayResize(m_array,size);}
   //--- tamanho de matriz 
   int               Size(void){return(ArraySize(m_array));}
   //--- alteração do tamanho da matriz 
   int               Resize(int size,int reserve){return(ArrayResize(m_array,size,reserve));}
   //--- libertação da matriz 
   void              Erase(void){ZeroMemory(m_array);}
   //--- operador de acesso ao elemento da matriz de acordo com o índice
   T                 operator[](int index);
   //--- operador de atribuição para obter imediatamente todos os elementos a partir da matriz
   void              operator=(const T  &array[]); // matriz do tipo T 
  };
//+------------------------------------------------------------------+
//| Operação de obtenção do elemento segundo o í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]);
  }
//+------------------------------------------------------------------+
//| Operação de atribuição para a matriz                                |
//+------------------------------------------------------------------+
template<typename T>
void TSafeArray::operator=(const T  &array[])
  {
   int size=ArraySize(array);
   ArrayResize(m_array,size);
//--- o tipo T deve suportar o operador de cópia
   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 a matriz de cotações
   if((copied=CopyRates(_Symbol,_Period,0,size,rates))!=size)
     {
      PrintFormat("CopyRates(%s,%s,0,%d) retornou o código de erro %d",
      _Symbol,EnumToString(_Period),size,GetLastError());
      return;
     }
//--- criamos o recipiente e colocamos nele a matriz dos valores MqlRates
   TSafeArray<MqlRates> safe_rates;
   safe_rates=rates;
   //--- índice nos limites da matriz
   int index=3;
   PrintFormat("Close[%d]=%G",index,safe_rates[index].close);
   //--- índice fora dos limites da matriz
   index=size;
   PrintFormat("Close[%d]=%G",index,safe_rates[index].close);
  }

Note-se que, na descrição dos métodos fora da declaração da classe, também é necessário utilizar a declaração de modelo:

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

Os modelos de classes e funções permitem especificar vários parâmetros formais, separados por vírgulas, por exemplo, coleção Map para armazenar os pares "chave - valor":

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

 

Veja também

Modelos de funções, Sobrecarga