Обратные вызовы в C++ | страница 10




>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 производится вызов функции по соответствующему указателю. Как видим, объектная реализация практически полностью повторяет процедурную, только все объявления сделаны внутри класса. Другими словами, мы провели инкапсуляцию данных и процедур внутри некоторой сущности, в качестве которой выступает класс.