Выбрать главу

Вот если бы мы могли для всех аргументов использовать единый общий параметр, тогда все реализации могли быть описаны с помощью одного единственного шаблона. Решить эту задачу можно путем преобразования вызовов.

4.2.2. Преобразование вызовов

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

Вначале рассмотрим вызовы через указатели на функцию. Создадим шаблон для функционального объекта, в котором будем хранить указатель на функцию и контекст. Перегрузим оператор вызова функции, в реализации которого по хранимому указателю вызовем функцию-обработчик и передадим ей хранимый контекст (Листинг 27).

Листинг 27. Функциональный объект для вызова функции с передачей контекста

template<typename Function, typename Context>  // (1)

class CallbackConverter  // (2)

{

public:

  CallbackConverter (Function argFunction = nullptr, Context argContext = nullptr)  // (3)

  {

    ptrFunction = argFunction; context = argContext;

  }

  void operator() (int eventID)      // (4)

  {

     ptrFunction(eventID, context);  // (5)

  }

private:

  Function ptrFunction;  // (6)

  Context context;       // (7)

};

В строке 1 объявлен шаблон с двумя параметрами – тип указателя на функцию и тип для контекста. В строке 2 объявлено имя класса. В строке 3 объявлен конструктор, который будет сохранять требуемые значения – указатель на функцию и указатель на контекст, переменные для хранения объявлены в строках 6 и 7. В строке 4 осуществляется перегрузка оператора вызова функции, который делает обратный вызов, передавая информацию и сохраненный контекст.

Рассмотренный шаблон также будет работать для указателей на статический метод класса, только необходимо объявить соответствующие типы указателей.

Для указателей на метод-член класса сделаем специализацию шаблона, как это показано в Листинг 28.

Листинг 28. Функциональный объект для вызова метода класса

template<typename ClassName>  // (1)

class CallbackConverter <void(ClassName::*)(int), ClassName>  // (2)

{

public:

  using ClassMethod = void(ClassName::*)(int);  // (3)

  CallbackConverter(ClassMethod methodPointer = nullptr, ClassName* classPointer = nullptr)  // (4)

  {

    ptrClass = classPointer; ptrMethod = methodPointer;

  }

  void operator()(int eventID)       // (5)

  {

    ptrClass->*ptrMethod)(eventID);  // (6)

  }

private:

  ClassName* ptrClass;    // (7)

  ClassMethod ptrMethod;  // (8)

};

В строке 1 объявлен шаблон с параметром – именем класса. В строке 2 объявлена специализация шаблона из Листинг 27. Именно эта специализация будет выбрана компилятором, если шаблон инстанциируется указателем на метод класса и указателем на класс. В строке 3 объявлен тип – указатель на метод класса. Этот тип выводится из имени класса, поэтому в шаблоне одного параметра – имени класса – будет достаточно. В строке 4 объявляется конструктор, который будет сохранять требуемые значения – указатель на экземпляр класса и указатель на метод, переменные для хранения объявлены в строках 7 и 8. В строке 5 перегружается оператор вызова функции, который вызывает метод класса.

4.2.3. Исполнитель

Итак, определив объекты для преобразования вызовов, мы теперь можем использовать в шаблоне-инициаторе, определенном в Листинг 25 п. 4.2.1, любые типы аргументов обратного вызова. Пример приведен в Листинг 29.

Листинг 29. Исполнитель для шаблона-инициатора синхронного вызова

class Executor  // (1)

{

public:

  static void staticCallbackHandler(int eventID, Executor* executor) {}

  void callbackHandler(int eventID) {}

  void operator() (int eventID) {}

};

void ExternalHandler(int eventID, void* somePointer)

{

  Executor* ptrClass = (Executor*)somePointer;

}

int main()

{

  Executor executor;

  int capturedValue = 0;

  // (2) External function

  using FunctionPointer = void(*)(int, void*);

  using FunctionConverter = CallbackConverter<FunctionPointer, void*>;

вернуться

18

Здесь функциональный объект реализует паттерн «адаптер». Для знакомства с паттернами вообще, и с паттерном «адаптер» в частности можно порекомендовать книгу « Гамма Э., Хелм Р., Джонсон Р., Влиссидес Д. Приемы объектно-ориентированного проектирования. Паттерны проектирования».