Чем хороши шаблоны
Шаблоны функций используются в тех случаях, когда необходимо производить одинаковые операции с данными разного типа, например – поиск максимального элемента в массиве. Главное преимущество использования шаблонов заключается в том, что программисту нет необходимости писать отдельную перегрузку для каждого типа. То есть вместо объявления множества перегрузок для каждого типа
double ArrayMax(double array[])
{
...
}
int ArrayMax(int array[])
{
...
}
uint ArrayMax(uint array[])
{
...
}
long ArrayMax(long array[])
{
...
}
datetime ArrayMax(datetime array[])
{
...
} |
достаточно написать одну шаблонную функцию
template<typename T>
T ArrayMax(T array[])
{
if(ArraySize()==0)
return(0);
uint max_index=ArrayMaximum(array);
return(array[max_index]);
} |
и затем использовать её в своем коде:
double high[];
datetime time[];
....
double max_high=ArrayMax(high);
datetime lasttime=ArrayMax(time); |
Здесь формальный параметр T, который задает тип используемых данных, при компиляции заменяется на реально используемый тип, то есть компилятор автоматически генерирует отдельную функцию под каждый тип – double, datetime и так далее. Точно также в языке MQL5 можно создавать шаблоны классов, используя все преимущества такого подхода.
Шаблоны классов
Шаблон класса объявляется с помощью ключевого слов template, за которым идут угловые скобки <>, в которых перечисляется список формальных параметров с ключевым словом typename. Такая запись указывает компилятору, что перед ним обобщенный класс с формальным параметром T, задающим реальный тип переменной при реализации класса. Например, создадим класс-вектор для хранения массива с элементами типа T:
#define TOSTR(x) #x+" " // макрос для вывода имени объекта
//+------------------------------------------------------------------+
//| Класс-вектор для хранения элементов типа T |
//+------------------------------------------------------------------+
template <typename T>
class TArray
{
protected:
T m_array[];
public:
//--- конструктор по умолчанию создает массив на 10 элементов
void TArray(void){ArrayResize(m_array,10);}
//--- конструктор для создания вектора с заданным размером массива
void TArray(int size){ArrayResize(m_array,size);}
//--- возвращает тип и количество данных, которые хранятся в объекте типа TArray
string Type(void){return(typename(m_array[0])+":"+(string)ArraySize(m_array));};
}; |
Далее, в программе создадим разными способами три объекта TArray для работы с разными типами
void OnStart()
{
TArray<double> double_array; // вектор имеет размер по умолчанию 10
TArray<int> int_array(15); // вектор имеет размер 15
TArray<string> *string_array; // указатель на вектор TArray<string>
//--- создадим динамический объект
string_array=new TArray<string>(20);
//--- выведем в Журнал имя объекта, тип данных и размер вектора
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());
//--- удалим динамический объект перед завершением программы
delete(string_array);
} |
Результат выполнения скрипта:
double_array (double:10)
int_array (int:15)
string_array (string:20) |
В результате было создано 3 вектора с разными типами данных: double, int и string.
Шаблоны классов хорошо подходят для разработки контейнеров – объектов, которые предназначены для инкапсуляции объектов любого типа. Объектами контейнеров являются коллекции, которые уже содержат объекты одного определенного типа. Как правило в контейнер сразу же встраивается и реализация по работе с данными, которые в нём хранятся.
Например, можно создать шаблон класса, который не позволяет обратиться к элементу за пределами массива, и таким образом избегать критической ошибки "out of range".
//+------------------------------------------------------------------+
//| Класс для безопасного обращения к элементу массива |
//+------------------------------------------------------------------+
template<typename T>
class TSafeArray
{
protected:
T m_array[];
public:
//--- конструктор по умолчанию
void TSafeArray(void){}
//--- конструктор для создания массива заданного размера
void TSafeArray(int size){ArrayResize(m_array,size);}
//--- размер массива
int Size(void){return(ArraySize(m_array));}
//--- изменение размера массива
int Resize(int size,int reserve){return(ArrayResize(m_array,size,reserve));}
//--- освобождение массива
void Erase(void){ZeroMemory(m_array);}
//--- оператор доступа к элементу массива по индексу
T operator[](int index);
//--- оператор присваивания для получения сразу всех элементов из массива
void operator=(const T &array[]); // массив типа T
};
//+------------------------------------------------------------------+
//| Операция получения элемента по индексу |
//+------------------------------------------------------------------+
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]);
}
//+------------------------------------------------------------------+
//| Операция присваивания для массива |
//+------------------------------------------------------------------+
template<typename T>
void TSafeArray::operator=(const T &array[])
{
int size=ArraySize(array);
ArrayResize(m_array,size);
//--- тип T должен поддерживать оператор копирования
for(int i=0;i<size;i++)
m_array[i]=array[i];
//---
}
//+------------------------------------------------------------------+
//| Script program start function |
//+------------------------------------------------------------------+
void OnStart()
{
int copied,size=15;
MqlRates rates[];
//--- скопируем массив котировок
if((copied=CopyRates(_Symbol,_Period,0,size,rates))!=size)
{
PrintFormat("CopyRates(%s,%s,0,%d) вернула код ошибки %d",
_Symbol,EnumToString(_Period),size,GetLastError());
return;
}
//--- создадим контейнер и вложим в него массив значений MqlRates
TSafeArray<MqlRates> safe_rates;
safe_rates=rates;
//--- индекс в пределах массива
int index=3;
PrintFormat("Close[%d]=%G",index,safe_rates[index].close);
//--- индекс за пределами массива
index=size;
PrintFormat("Close[%d]=%G",index,safe_rates[index].close);
} |
Обратите внимание, что при описании методов за пределами декларации класса также необходимо использовать объявление шаблона:
template<typename T>
T TSafeArray::operator[](int index)
{
...
}
template<typename T>
void TSafeArray::operator=(const T &array[])
{
...
} |
Шаблоны классов и функций позволяют указывать несколько формальных параметров через запятую, например, коллекция Map для хранения пар "ключ – значение":
template<typename Key, template Value>
class TMap
{
...
} |
Смотри также
Шаблоны функций, Перегрузка