Обратные вызовы в C++ - стр. 8
>class Initiator  //(1)
>{
>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).
2.1.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 инициатор запускается.