Указатели на функции (typedef)

MQL5 имеет ключевое слово typedef, которое позволяет описать специальный тип указателя на функцию.

В отличие от C++, где typedef имеет гораздо более широкое применение, в MQL5 typedef используется только для указателей на функции.

Синтаксис описания нового типа таков:

typedef function_result_type ( *function_type )( [list_of_input_parameters] ) ;

Идентификатор function_type определяет имя типа, которое становится синонимом (алиасом) указателя на любую функцию, возвращающую значение заданного типа function_result_type и принимающую список входных параметров (list_of_input_parameters).

Например, у нас может быть 2 функции с одинаковыми прототипами (два входных параметра типа double и тип результата тоже double), выполняющие разные арифметические операции: сложение и вычитание (FuncTypedef.mq5).

double plus(double v1double v2)
{
   return v1 + v2;
}
 
double minus(double v1double v2)
{
   return v1 - v2;
}

Их общий прототип легко описать для использования в качестве указателя:

typedef double (*Calc)(doubledouble);

Данная запись вводит в программу тип Calc, с помощью которого можно определить переменную/параметр для хранения/передачи "ссылки" на любую функцию с таким прототипом, включая обе функции plus и minus. Указателем данный тип является потому, что в описании используется символ '*' (*Calc). Об особенностях "звездочки" в применении к указателям мы более подробно узнаем при изучении ООП.

Подобный класс указателей удобно использовать для создания настраиваемых алгоритмов, способных "на лету", в зависимости от входных данных, вызывать разные функции, соответствующие алиасу.

В частности, у нас есть возможность ввести обобщенную функцию-вычислитель:

double calculator(Calc ptrdouble v1double v2)
{
   if(ptr == NULLreturn 0;
   return ptr(v1v2);
}

Её первый параметр описан с типом Calc. За счет этого мы можем передать в неё произвольную функцию с подходящим прототипом и в результате выполнить некую операцию, о сути которой сама функция calculator не знает. Она делает это путем делегирования вызова указателю: ptr(v1, v2). Поскольку ptr это указатель на функцию, данный синтаксис не только напоминает вызов функции, но и реально вызывает функцию, которую указатель хранит.

Обратите внимание, что мы предварительно проверяем параметр ptr на специальное значение NULL (NULL — эквивалент нуля для указателей). Дело в том, что указатель может никуда не указывать, то есть быть непроинициализированным. Так, в скрипте у нас описана глобальная переменная:

Calc calc;

В ней как раз нет никакого указателя. Если бы не "защита" от NULL, вызов calculator с "пустым" указателем calc привел бы к ошибке времени исполнения "Вызов функции по некорректному указателю" ("Invalid function pointer call").

Вызовы функции calculator с разными указателями в первом параметре дадут следующие результаты (показаны в комментариях):

void OnStart()
{
   Print(calculator(plus12));   //  3
   Print(calculator(minus12));  // -1
   Print(calculator(calc12));   //  0
}

Отметим, что все указатели на функции в отсутствие явной инициализации, заполняются нулевыми значениями. Это касается и глобальных, и локальных переменных данного типа.

Тип указателя, определенный с помощью typedef, можно возвращать из функций, например:

Calc generator(ushort type)
{
   switch(type)
   {
      case '+': return plus;
      case '-': return minus;
   }
   return NULL;
}

Кроме того, тип указателей на функции часто используется для функций обратного вызова (callback, см. FuncCallback.mq5). Представьте, что у нас есть функция DoMath, которая выполняет длительные вычисления (вполне вероятно, она реализована в отдельной библиотеке). С точки зрения удобства и дружественности пользовательского интерфейса было бы здорово показывать пользователю индикацию прогресса. Для этой цели можно определить специальный тип указателя на функцию для уведомлений о проценте выполненной работы (ProgressCallback), а в функцию DoMath добавить параметр этого типа. В коде DoMath следует периодически вызывать переданную функцию:

typedef void (*ProgressCallback)(const float percent);
 
void DoMath(double &bigdata[], ProgressCallback callback)
{
   const int N = 1000000;
   for(int i = 0i < N; ++i)
   {
      if(i % 10000 == 0 && callback != NULL)
      {
         callback(i * 100.0f / N);
      }
      
      // долгие вычисления
   }
}

Тогда вызывающий код может определить у себя требуемую функцию-callback, передать указатель на неё в DoMath и получать обновления по ходу вычислений.

void MyCallback(const float percent)
{
   Print(percent);
}
  
void OnStart()
{
   double data[] = {0};
   DoMath(dataMyCallback);
}

Указатели на функции работают только с пользовательскими функциями, определенными в MQL5. Они не могут указывать на встроенные функции MQL5 API.