Применительно к нашей теме, т. е. проектированию обратных вызовов, с помощью шаблонов можно реализовать множество интересных вещей, как это будет показано в следующих главах. Начнем с синхронных вызовов, как наиболее простых.
Проанализируем различные реализации инициатора синхронных вызовов (Листинг 24):
Листинг 24. Реализации инициатора для синхронных вызовов
class Executor
{
public:
void callbackHandler(int eventID);
void operator() (int eventID);
};
using ptr_callback = void(*) (int, void*);
using ptr_callback_static = void(*) (int, Executor*);
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);
}
Однако такое решение противоречит идее обобщенного кода: для нового типа данных мы реализуем новый код, который дублирует предыдущий, за исключением самого вызова. Как следствие, если в коде инициатора нужно сделать изменения, их придется переносить на все объявленные функции. Но это еще не все: указанное решение не покрывает случая использования указателей на метод-член класса, синтаксис вызова которого отличается от синтаксиса вызова внешней функции. Таким образом, придется реализовать еще один шаблон функции для вызова метода класса. При этом он должен будет иметь другое имя, иначе возникнет конфликт с предыдущим определением: количество входных параметров одинаково, и компилятор не знает, какую реализацию шаблона подставлять при инстанциировании.
Вот если бы мы могли для всех аргументов использовать единый общий параметр, тогда все реализации могли быть описаны с помощью одного единственного шаблона. Решить эту задачу можно путем преобразования вызовов.
4.2.2. Преобразование вызовов
Для преобразования вызовов используется функциональный объект, в котором хранятся данные, необходимые для осуществления обратного вызова. Объявляется перегруженный оператор, который принимает информацию вызова. Реализация оператора выполняет требуемый вызов, передавая ему на вход полученную информацию вызова и, дополнительно, хранимые данные 18 18 Здесь функциональный объект реализует паттерн «адаптер». Для знакомства с паттернами вообще, и с паттерном «адаптер» в частности можно порекомендовать книгу « Гамма Э., Хелм Р., Джонсон Р., Влиссидес Д. Приемы объектно-ориентированного проектирования. Паттерны проектирования».
.
Вначале рассмотрим вызовы через указатели на функцию. Создадим шаблон для функционального объекта, в котором будем хранить указатель на функцию и контекст. Перегрузим оператор вызова функции, в реализации которого по хранимому указателю вызовем функцию-обработчик и передадим ей хранимый контекст (Листинг 27).
Листинг 27. Функциональный объект для вызова функции с передачей контекста
template // (1)
Читать дальше