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 // (1)
class CallbackConverter // (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 перегружается оператор вызова функции, который вызывает метод класса.
Итак, определив объекты для преобразования вызовов, мы теперь можем использовать в шаблоне-инициаторе, определенном в Листинг 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;
run(FunctionConverter(ExternalHandler, &executor));
// (3) Static method
using StaticPointer = void(*)(int, Executor*);
using StaticConverter = CallbackConverter;
run(StaticConverter(Executor::staticCallbackHandler, &executor));
// (4) Member merthod
using MethodPointer = void(Executor::*)(int);
using MethodConverter = CallbackConverter;
run(MethodConverter(&Executor::callbackHandler, &executor));
// (5) Functional object
run(executor);
// (6) lambda-expression
auto lambda = [capturedValue](int eventID) {/*it will be called by initiator*/};
run(lambda);
}
В строке 1 объявлен класс исполнителя, в котором определены все необходимые типы вызовов: статический метод, метод-член, перегруженный оператор. Для вызовов 2, 3 и 4 в качестве аргумента передается функциональный объект для преобразования, который инстанциируется соответствующими типами. В остальных случаях нужный аргумент передается непосредственно, преобразования вызовов там не нужно. При использовании лямбда-выражения (строка 6) компилятор неявно определит его тип и подставит его в функцию шаблона-инициатора как аргумент.
При использовании преобразования вызовов можно использовать сокращенную запись без дополнительного объявления промежуточных типов, в этом случае код получается более компактным, но более запутанным (см. Листинг 30)
Листинг 30. Преобразование вызовов без объявления промежуточных типов
// (2) External function
run(CallbackConverter(ExternalHandler, &executor));
// (3) Static method
run(CallbackConverter(Executor::staticCallbackHandler, &executor));
Читать дальше