//Wait finish
}
В строке 1 объявляется класс – исполнитель. В строке 2 объявляется конструктор с входным параметром – указателем на инициатор, здесь происходит настройка обратного вызова.5
В строке 3 объявлен статический метод как обработчик обратного вызова. Входными параметрами здесь являются информация вызова (в нашем случае это eventID) и указатель на контекст, в качестве которого выступает указатель на экземпляр класса. Внутри метода можно обращаться к содержимому класса, используя полученный указатель как квалификатор. Таким образом, прямо здесь можно реализовать код обработчика, а можно вызвать обычный (нестатический) метод класса (строка 4).
Далее, в строке 6 объявлена основная функция, в которой осуществляются все необходимые операции. В строке 7 объявлен класс-инициатор; в строке 8 объявлен класс- исполнитель, в конструктор передается указатель на инициатор; в строке 9 происходит запуск инициатора.
Особенностью реализации исполнителя с помощью указателя на статический метод является возможность работы с инициатором, предназначенным для указателей на функцию. В этом случае метод класса в качестве контекста должен принимать нетипизированный указатель с последующим приведением типов. Пример использования показан в Листинг 8, инициатор здесь используется из Листинг 1 п. 2.1.2.
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. Как видим, она практически полностью повторяет реализацию, рассмотренную в предыдущей главе, только в качестве указателя на контекст используется указатель на экземпляр класса.
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. Исполнитель реализуется в виде класса, код упаковывается в метод класса, в качестве контекста выступает экземпляр класса. При настройке указатель на метод и указатель на класс как как аргументы сохраняются в инициаторе. Инициатор осуществляет обратный вызов посредством вызова метода, передавая ему требуемую информацию. Контекст здесь передавать не нужно, поскольку внутри метода доступно все содержимое класса.
5
Это необязательно делать в конструкторе, соответствующие операции можно выполнить после объявлений экземпляров инициатора и исполнителя в функции main. Однако инициализация в конструкторе представляется более удобной, потому что настройка вызова будет сделана сразу при объявлении экземпляра класса – исполнителя без дополнительных операций.