Всего за 0.01 руб. Купить полную версию
struct СontextData // (1)
{
//some context data
};
void callbackHandler(int eventID, void* somePointer) // (2)
{
//It will be called by initiator
СontextData* pContextData = static_cast<СontextData*>(somePointer); // (3) cast to context
//Do something here
}
int main() // (4)
{
Initiator initiator; // (5)
СontextData clientContext; // (6)
initiator.setup(callbackHandler, &clientContext); // (7) callback setup
initiator.run(); // (8) initiator has been run
//Wait finish
}
2.1.4. Синхронный вызов
Реализация инициатора для синхронного вызова приведена в Листинг 5. Как видим, для синхронных вызовов код значительно упрощается: нет необходимости хранить переменные, информация вызова и контекст передаются непосредственно в функцию.
Листинг 5. Инициатор для синхронного обратного вызова с указателем на функциюusing ptr_callback = void(*) (int, void*);
void run(ptr_callback ptrCallback, void* contextData = nullptr)
{
int eventID = 0;
//Some actions
ptrCallback (eventID, contextData);
}
2.1.5. Преимущества и недостатки
Достоинства и недостатки реализации обратных вызовов с помощью указателя на функцию представлены в Табл. 1.
Табл. 1. Преимущества и недостатки обратных вызовов с указателем на функцию
Простая реализация. Как мы видели, инициатор реализуется достаточно просто: две переменных, синтаксис вызова функции через указатель очень похож на вызов обычной функции.
Независимость инициатора и исполнителя. Любое изменение кода исполнителя никак не влияет на код инициатора, который при этом остается неизменным
Совместим с кодом на языке C. В некоторых случаях приходится разрабатывать смешанный код, т. е. часть кода пишется C, а часть на С++. Если код исполнителя написан на C++, и этот код должен быть вызван инициатором, написанным на C, то использование указателей на функцию является единственно доступным механизмом. 4
Подходит для реализации любых API. Можно реализовать как С++, так и системные API. Для C++ API инициатор разрабатывается в виде набора классов, для системных API в виде набора функций.
Инициатор хранит контекст исполнителя. Как мы видели, инициатор вынужден сохранять контекст исполнителя. Это усложняет реализацию и способствует увеличению расхода памяти.
Небезопасный способ трансляции контекста. Контекст передается клиенту в виде нетипизированного указателя, интерпретация указателя возлагается на клиента. В большой программной системе это чревато ошибками, поскольку нет никакой возможности проверить корректность полученного указателя.
2.2. Указатель на статический метод класса
2.2.1. Концепция
Графическое изображение обратного вызова с помощью указателя на статический метод класса представлено на Рис. 11. Исполнитель реализуется в виде класса, код упаковывается в статический метод класса, в качестве контекста выступает указатель на экземпляр класса. При настройке указатель на статический метод как аргумент и указатель на класс как контекст сохраняются в инициаторе. Инициатор осуществляет обратный вызов посредством вызова метода, передавая ему требуемую информацию и контекст указатель на класс.
Рис. 11. Обратный вызов с указателем на статический метод класса
2.2.2. Инициатор
По своей сути статический метод класса это обычная функция, ограниченная областью видимости класса. Поэтому реализация инициатора, представленная в Листинг 6, практически полностью повторяет реализацию для указателей на функцию, только в качестве контекста выступает указатель на экземпляр класса.
Листинг 6. Инициатор с указателем на статический метод классаclass Executor; //(1)
class Initiator // (2)
{
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)