В строке 9 мы в качестве предшественника указываем класс Executor1 и назначаем указатель на метод callbackHandler1. В Executor1 этот метод переопределен, и он будет вызван. В строке 10 мы назначаем указатель на метод callbackHandler2; в Executor1 этот метод не переопределен, поэтому будет вызван метод базового класса Executor::callbackHandler2. Если мы в качестве предшественника будем указывать Executor2, как это показано в строках 11 и 12, то получится все наоборот: в строке 11 будет вызван метод базового класса Executor:: callbackHandler1, а в строке 12 будет вызван соответствующий переопределенный метод Executor2::callbackHandler2.
Для наглядности сведем результаты в Табл. 3.
Табл. 3. Вызовы методов по цепочке наследования
Используя рассмотренные способы управления контекстом, можно реализовать довольно изощренную логику обработки и динамически ее изменять в процессе выполнения программы.
2.3.5. Синхронный вызов
Реализация инициатора для синхронного вызова представлена в Листинг 14. В отличие от асинхронного вызова, здесь аргументы не хранятся, а передаются как входные параметры функции.
class Executor;
using ptr_method_callback_t = void(Executor::*)(int);
void run(Executor* ptrClientCallbackClass, ptr_method_callback_t ptrClientCallbackMethod)
{
int eventID = 0;
//Some actions
(ptrClientCallbackClass->*ptrClientCallbackMethod)(eventID);
}
2.3.6. Преимущества и недостатки
Преимущества и недостатки реализации обратных вызовов с помощью указателя на метод – член класса приведены в Табл. 4.
Табл. 4. Преимущества и недостатки реализации обратных вызовов с помощью указателя на метод-член класса
2.4. Функциональный объект
2.4.1. Концепция
С точки зрения C++ функциональный объект – это класс, который имеет перегруженный оператор вызова функции7.
Графическое изображение обратного вызова с помощью функционального объекта представлено на Рис. 14. Исполнитель реализуется в виде класса, код упаковывается в перегруженный оператор вызовы функции, в качестве контекста выступает экземпляр класса. При настройке экземпляр класса как аргумент сохраняется в инициаторе8. Инициатор осуществляет обратный вызов посредством вызова перегруженного оператора, передавая ему требуемую информацию. Контекст здесь передавать не нужно, поскольку внутри оператора доступно все содержимое класса.
Рис. 14. Реализация обратного вызова с помощью функционального объекта.
2.4.2. Инициатор
Предварительно необходимо объявить функциональный объект (см. Листинг 15), потому что его объявление должен видеть как инициатор, так и исполнитель.
class CallbackHandler
{
public:
void operator() (int eventID) //This is an overloaded operator
{
//It will be called by server
};
};
Реализация инициатора приведена в Листинг 16.
class Initiator // (1)
{
public:
void setup(const CallbackHandler& callback) // (2)
{
callbackObject = callback;
}
void run() // (3)
{
int eventID = 0;