Обратные вызовы в C++ - Виталий Евгеньевич Ткаченко 5 стр.


{

public:

  using ptr_callback_static = void(*) (int, Executor*);                 // (3)


  void setup(ptr_callback_static pPtrCallback, Executor* pContextData)  // (4)

  {

    ptrCallback = pPtrCallback; contextData = pContextData;             // (5)

  }


  void run()                           //  (6)

  {

    int eventID = 0;

    //Some actions

    ptrCallback(eventID, contextData);  // (7)

  }


private:

  ptr_callback_static ptrCallback = nullptr;  // (8)

  Executor* contextData = nullptr;            // (9)

};


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

2.2.3. Исполнитель

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

Листинг 7. Исполнитель с указателем на статический метод класса

class Executor                    // (1)

{

public:

  Executor(Initiator* initiator)  // (2)

  {

    initiator->setup(callbackHandler, this);

  }


  static void callbackHandler(int eventID, Executor* executor)  // (3)

  {

    //It will be called by initiator

    executor->onCallbackHandler(eventID);                       // (4)

  }


private:

  void onCallbackHandler(int eventID)  // (5)

  {

    //Do what is necessary

  }

};


int main() // (6)

{

  Initiator initiator;            // (7)

  Executor executor(&initiator);  // (8)

  initiator.run();                // (9)

  //Wait finish

}


В строке 1 объявляется класс исполнитель. В строке 2 объявляется конструктор с входным параметром  указателем на инициатор, здесь происходит настройка обратного вызова.5

В строке 3 объявлен статический метод как обработчик обратного вызова. Входными параметрами здесь являются информация вызова (в нашем случае это eventID) и указатель на контекст, в качестве которого выступает указатель на экземпляр класса. Внутри метода можно обращаться к содержимому класса, используя полученный указатель как квалификатор. Таким образом, прямо здесь можно реализовать код обработчика, а можно вызвать обычный (нестатический) метод класса (строка 4).

Далее, в строке 6 объявлена основная функция, в которой осуществляются все необходимые операции. В строке 7 объявлен класс-инициатор; в строке 8 объявлен класс- исполнитель, в конструктор передается указатель на инициатор; в строке 9 происходит запуск инициатора.

Особенностью реализации исполнителя с помощью указателя на статический метод является возможность работы с инициатором, предназначенным для указателей на функцию. В этом случае метод класса в качестве контекста должен принимать нетипизированный указатель с последующим приведением типов. Пример использования показан в Листинг 8, инициатор здесь используется из Листинг 1 п. 2.1.2.

Листинг 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<Executor*>(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)

  }


private:

  Executor* ptrCallbackClass = nullptr;             // (8)

  ptr_callback_method ptrCallbackMethod = nullptr;  // (9)

};


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

2.3.3. Исполнитель

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

Листинг 11. Исполнитель с указателем на метод-член класса

class Executor                       // (1)

{

public:

  void callbackHandler(int eventID)  // (2)

  {

    //It will be called by initiator

  }

};


int main()                                                 // (3)

{

  Initiator initiator;                                     // (4)

  Executor executor;                                       // (5)

  initiator.setup(&executor, &Executor::callbackHandler);  // (6)

  initiator.run();                                         // (7)

Назад Дальше