Листинг 8. Исполнитель с указателем на статический метод класса для инициатора с нетипизированным контекстом

>class Executor  // (1)

>{

>public:

>  Executor()    // (2)

>  {

>    setup(callbackHandler, this);

>  }


>  static void callbackHandler(int eventID, void* somePointer)  // (3)

>  {

>    //It will be called by initiator

>    Executor* executor = static_cast(somePointer);  // (4)

>    executor->onCallbackHandler(eventID);

>  }


>private:

>  void onCallbackHandler(int eventID)  // (5)

>  {

>    //Do what is necessary

>  }

>};


>int main()            // (6)

>{

>  Executor executor;  // (7)

>  run();              // (8)

>  //Wait finish

>}


Настройка обратного вызова осуществляется в конструкторе (строка 2). В обработчике обратного вызова (строка 3) мы делаем приведение типов (строка 4), чтобы получить указатель на экземпляр класса. В главной функции (строка 6) происходит запуск инициатора.

2.2.4. Синхронный вызов

Реализация инициатора для синхронного вызова приведена в Листинг 9. Как видим, она практически полностью повторяет реализацию, рассмотренную в предыдущей главе, только в качестве указателя на контекст используется указатель на экземпляр класса.

Листинг 9. Инициатор для синхронного обратного вызова с указателем на статический метод класса

>class Executor;

>using ptr_callback_static = void(*) (int, Executor*);


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

>{

>  int eventID = 0;

>  //Some actions

>  ptrCallback (eventID, contextData);

>}

2.2.5. Преимущества и недостатки

Преимущества и недостатки реализации обратных вызовов с помощью указателя на статический метод класса приведены в Табл. 2.


Табл. 2. Преимущества и недостатки обратных вызовов с указателем на статический метод класса


Простая реализация. Не сложнее, чем для указателей на функцию.

Совместим с инициатором в процедурном дизайне. Можно использовать для работы с системными API.

Инициатор хранит контекст исполнителя. Так же, как и в случае указателей на функцию, усложняет реализацию и способствует увеличению расхода памяти.

2.3. Указатель на метод-член класса

2.3.1. Концепция

В предыдущей главе мы рассматривали использование указателя на статический метод класса, в который в качестве контекста передавали указатель на экземпляр класса. А почему бы нам напрямую не вызвать метод-член класса, минуя прослойку в виде статического метода, из которого вызывается метод-член класса? Для этого нам понадобятся указатель на класс и указатель на метод.

Графическое изображение обратного вызова с помощью указателя на метод-член класса (далее – метод класса) представлено на Рис. 12. Исполнитель реализуется в виде класса, код упаковывается в метод класса, в качестве контекста выступает экземпляр класса. При настройке указатель на метод и указатель на класс как как аргументы сохраняются в инициаторе. Инициатор осуществляет обратный вызов посредством вызова метода, передавая ему требуемую информацию. Контекст здесь передавать не нужно, поскольку внутри метода доступно все содержимое класса.


Рис. 12. Реализация обратного вызова с помощью указателя на метод-член класса


2.3.2. Инициатор

Реализация инициатора приведена в Листинг 10.

Листинг 10. Инициатор с указателем на метод-член класса

>class Executor;  // (1)


>class Initiator  // (2)

>{

>public:

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


>  void setup(Executor* argCallbackClass, ptr_callback_method argCallbackMethod)  // (4)

>  {

>    ptrCallbackClass = argCallbackClass; ptrCallbackMethod = argCallbackMethod;  // (5)

>  }


>  void run()  // (6)

>  {

>    int eventID = 0;

>    //Some actions

>    (ptrCallbackClass->*ptrCallbackMethod)(eventID);  // (7)