Обратные вызовы в C++ | страница 31



>using ptr_callback_method = void(Executor::*)(int);


>void run(ptr_callback ptrCallback, void* contextData = nullptr)  // (1)

>{

>  int eventID = 0;

>  ptrCallback(eventID, contextData);

>}


>void run(ptr_callback_static ptrCallback, Executor* contextData = nullptr)  // (2)

>{

>  int eventID = 0;

>  ptrCallback(eventID, contextData);

>}


>void run(Executor* ptrClientCallbackClass, ptr_callback_method ptrClientCallbackMethod)  // (3)

>{

>  int eventID = 0;

>  (ptrClientCallbackClass->*ptrClientCallbackMethod)(eventID);

>}


>void run(Executor callbackHandler)  // (4)

>{

>  int eventID = 0;

>  callbackHandler(eventID);

>}


Можно заметить, что все реализации, по сути, одинаковы, отличаются только типы и количество входных аргументов. Поэтому, можно попытаться сделать шаблон. Возьмем наиболее простой случай, когда функция на вход принимает только один параметр (Листинг 25):

Листинг 25. Шаблон для инициатора синхронного вызова

>template 

>void run(CallbackArgument callbackHandler)

>{

>  int eventID = 0;

>  //Some actions

>  callbackHandler(eventID);

>}


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

Что же нам делать для остальных реализаций? Для указателей на функцию и указателей на статический метод (строки 1 и 2) можно сделать отдельный шаблон с двумя параметрами (Листинг 26):

Листинг 26. Шаблон для инициатора с двумя параметрами

>template 

>void run(CallbackArgument callbackHandler, Context* context)

>{

>  int eventID = 0;

>  //Some actions

>  callbackHandler(eventID, context);

>}


Однако такое решение противоречит идее обобщенного кода: для нового типа данных мы реализуем новый код, который дублирует предыдущий, за исключением самого вызова. Как следствие, если в коде инициатора нужно сделать изменения, их придется переносить на все объявленные функции. Но это еще не все: указанное решение не покрывает случая использования указателей на метод-член класса, синтаксис вызова которого отличается от синтаксиса вызова внешней функции. Таким образом, придется реализовать еще один шаблон функции для вызова метода класса. При этом он должен будет иметь другое имя, иначе возникнет конфликт с предыдущим определением: количество входных параметров одинаково, и компилятор не знает, какую реализацию шаблона подставлять при инстанциировании.