Русский

Справочник MQL4 Основы языка Объектно-ориентированное программирование Шаблоны классов

Чем хороши шаблоны

Шаблоны функций используются в тех случаях, когда необходимо производить одинаковые операции с данными разного типа, например – поиск максимального элемента в массиве.  Главное преимущество использования шаблонов заключается в том, что программисту нет необходимости писать отдельную перегрузку для каждого типа.  То есть вместо объявления множества перегрузок для каждого типа

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

 

Смотри также

Шаблоны функций, Перегрузка