Графическое изображение реализации обратного вызова с помощью указателя на функцию представлено на Рис. 10. Исполнитель реализован в виде глобальной функции, в качестве контекста могут выступать любые данные. При настройке указатель на функцию как аргумент и указатель на данные как контекст сохраняются в инициаторе. Инициатор осуществляет обратный вызов посредством вызова функции через сохраненный указатель, передавая ей требуемые значения и контекст – указатель на данные. Поскольку инициатор не интерпретирует контекст и не выполняет с ним никаких операций, для хранения контекста используется нетипизированный указатель.
Рис. 10. Обратный вызов с указателем на функцию
Реализация инициатора представлена в Листинг 12.
Листинг 1.Иинициатор с указателем на функцию
>typedef void(*ptr_callback) (int eventID, void* pContextData); // (1)
>ptr_callback ptrCallback = NULL; // (2)
>void* contextData = NULL; // (3)
>void setup(ptr_callback pPtrCallback, void* pContextData) // (4)
>{
> ptrCallback = pPtrCallback;
> contextData = pContextData;
>}
>void run() // (5)
>{
> int eventID = 0;
> //Some actions
> ptrCallback(eventID, contextData); // (6)
>}
В строке 1 объявлен тип – указатель на функцию, в строке 2 объявлена переменная этого типа, в строке 3 объявлен указатель на данные контекста. В строке 4 объявлена функция для настройки указателей, в которой инициализируются соответствующие переменные. В строке 5 объявлена функция запуска, внутри этой функции инициатор в строке 6 производит вызов функции по сохраненному указателю. Сигнатура функции, объявленная в строке 1, в качестве первого параметра принимает значение, которое передается инициатором, т. е. информацию вызова, а второй параметр – это контекст. Указанная сигнатура здесь только для примера; конечно же, в зависимости от поставленных задач количество параметров и их порядок может быть произвольным. Мы также опустили моменты, связанные с созданием потока, ожиданием окончания работы сервера и т. п. – для понимания принципов организации вызова это несущественно.
Итак, мы реализовали инициатор в процедурно-ориентированном дизайне. Приведенная реализация имеет серьезный недостаток: указатель на функцию и указатель на контекст хранятся в глобальных переменных. Это создает множество проблем: изменения настроек указателей в разных частях программы не изолированы, т. е. влияют друг на друга; инициатор может работать только с одним-единственным исполнителем; невозможна одновременная работа нескольких потоков. Выходом из сложившейся ситуации будет реализация инициатора в объектно-ориентированном дизайне3 (Листинг 2).
Листинг 2. Инициатор с указателем на функцию в объектно-ориентированном дизайне
>class Initiator //(1)
>{
>public:
> using ptr_callback = void(*) (int, void*); //(2)
> void setup(ptr_callback pPtrCallback, void* pContextData) // (3)
> {
> ptrCallback = pPtrCallback; contextData = pContextData; // (4)
> }
> void run() // (5)
> {
> int eventID = 0;
> //Some actions
> ptrCallback (eventID, contextData); // (6)