Графическое изображение реализации обратного вызова с помощью указателя на функцию представлено на Рис. 10. Исполнитель реализован в виде глобальной функции, в качестве контекста могут выступать любые данные. При настройке указатель на функцию как аргумент и указатель на данные как контекст сохраняются в инициаторе. Инициатор осуществляет обратный вызов посредством вызова функции через сохраненный указатель, передавая ей требуемые значения и контекст – указатель на данные. Поскольку инициатор не интерпретирует контекст и не выполняет с ним никаких операций, для хранения контекста используется нетипизированный указатель.

Рис. 10. Обратный вызов с указателем на функцию

<p>2.1.2. Инициатор</p>

Реализация инициатора представлена в Листинг 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)

}

private:

  ptr_callback ptrCallback = nullptr;      // (7)

  void* contextData = nullptr;             // (8)

};

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

Перейти на страницу:

Похожие книги