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
{
...
}