public:
using ptr_callback = void(*) (int, void*); //(2)
void setup(ptr_callback pPtrCallback, void* pContextData) // (3)
{
ptrCallback = pPtrCallback; contextData = pContextData; // (4)
}
void run() // (5)
{
int eventID = 0;
//Some actions
ptrCallback (eventID, contextData); // (6)
}
private:
ptr_callback ptrCallback = nullptr; // (7)
void* contextData = nullptr; // (8)
};
В строке 1 мы объявляем класс – инициатор, в строке 2 мы объявляем тип указателя на функцию. В строке 3 объявляем функцию настройки указателей, соответствующие переменные – (указатель на функцию и указатель на контекст) объявлены соответственно в строках 7 и 8. В строке 5 объявлена функция запуска, внутри этой функции в строке 6 производится вызов функции по соответствующему указателю. Как видим, объектная реализация практически полностью повторяет процедурную, только все объявления сделаны внутри класса. Другими словами, мы провели инкапсуляцию данных и процедур внутри некоторой сущности, в качестве которой выступает класс.
Конечно, поскольку мы программируем на C++, мы должны следовать объектно-ориентированному дизайну, и любые реализации делать в его рамках. Для чего тогда мы привели реализацию инициатора в процедурном дизайне, в стиле языка C? Дело в том, что процедурный дизайн является единственно возможным для проектирования системных API, поскольку в объявлениях интерфейсов таких API допускается использование только глобальных функций и простых структур данных (см. п. 1.4.2).
Реализация исполнителя для случая, когда инициатор разработан в процедурном дизайне, представлена в Листинг 3.
Листинг 3. Исполнитель для инициатора в процедурном дизайне
struct СontextData // (1)
{
//some context data
};
void callbackHandler(int eventID, void* somePointer) // (2)
{
//It will be called by initiator
СontextData* pContextData = (СontextData*)somePointer; // (3)
//Do something here
}
int main() // (4)
{
СontextData clientContext; // (5)
setup(callbackHandler, &clientContext); // (6)
run(); // (7)
//Wait finish
}
В строке 1 объявляется тип данных для контекста. Структура здесь показана для примера, в качестве контекста могут выступать любые типы: числа, указатели, смеси и т. п. В строке 2 объявляется функция – обработчик обратного вызова, ее сигнатура должна совпадать с сигнатурой, с которой работает инициатор. Указанная функция будет вызвана инициатором, в нее будут переданы два параметра: первый передается инициатором (информация вызова, в нашем случае это eventID), а второй – это контекст. Клиент должен интерпретировать контекст; нет другого способа это сделать, кроме как приведением типов (строка 3).
Далее, в строке 4 объявлена основная функция, в которой осуществляются все необходимые операции. В строке 5 объявляются данные контекста; в строке 6 производится настройка обратного вызова, в функцию настройки передаются указатель на функцию-обработчик и указатель на контекст; в строке 7 инициатор запускается.
Реализация исполнителя для случая, когда инициатор реализован в объектно-ориентированном дизайне, представлена в Листинг 4. Как видим, она очень похожа на предыдущую реализацию с той разницей, что мы объявляем экземпляр класса-инициатора (строка 5), и все вызовы осуществляем через вызов соответствующих методов класса.
Листинг 4. Исполнитель для инициатора в объектно-ориентированном дизайне
struct СontextData // (1)
{
//some context data
};
void callbackHandler(int eventID, void* somePointer) // (2)
{
//It will be called by initiator
СontextData* pContextData = static_cast<���СontextData*>(somePointer); // (3) cast to context
//Do something here
}
int main() // (4)
{
Initiator initiator; // (5)
СontextData clientContext; // (6)
initiator.setup(callbackHandler, &clientContext); // (7) callback setup
initiator.run(); // (8) initiator has been run
//Wait finish
}
Реализация инициатора для синхронного вызова приведена в Листинг 5. Как видим, для синхронных вызовов код значительно упрощается: нет необходимости хранить переменные, информация вызова и контекст передаются непосредственно в функцию.
Листинг 5. Инициатор для синхронного обратного вызова с указателем на функцию
using ptr_callback = void(*) (int, void*);
void run(ptr_callback ptrCallback, void* contextData = nullptr)
{
int eventID = 0;
//Some actions
Читать дальше