Обратные вызовы в C++ - Виталий Евгеньевич Ткаченко 22 стр.


template<std::size_t Index, typename CallObjects, typename CallData>  // (1)

struct TupleIterator

{

  static void IterateTupleItem(CallObjects& callObjects, CallDatacallData)  // (2)

  {

    const std::size_t idx = std::tuple_size_v<CallObjects>  Index;  // (3)

    std::get<idx>(callObjects)(callData);                         // (4)

    TupleIterator<Index 1, CallObjects, CallData>::IterateTupleItem(callObjects, callData);  // (5)

  }

};


template<typename CallObjects, typename CallData>  // (6)

struct TupleIterator<0, CallObjects, CallData>     // (7)

{

  static void IterateTupleItem(CallObjects& callObjects, CallData callData)  // (8)

  {

  }

};


template<typename CallObjects, typename CallData>                         // (9)

void Distribute2(std::tuple<CallObjects> callObjects, CallData callData)  // (10)

{

  TupleIterator  // (11)

  <

  sizeof(CallObjects),      // (12)

  std::tuple<CallObjects>,  // (13)

  CallData                  // (14)

>

::IterateTupleItem(callObjects, callData);  // (15)

}


В строке 1 объявляется шаблон структуры. Параметрами шаблона выступают индекс элемента кортежа, сам кортеж и пакет параметров, который определяет данные, передаваемые в вызываемый объект.

Внутри структуры в строке 2 объявлена функция, осуществляющая выполнение вызова для элемента кортежа. Входными параметрами этой функции будет кортеж объектов вызова и пакет данных вызова, элемент кортежа определяется индексом параметром шаблона. Функция объявлена статической, чтобы не объявлять экземпляр структуры в процессе вызова. По сути дела, структура здесь не несет функциональной нагрузки, она выступает в качестве оболочки, чтобы обеспечить специализацию функции по индексу (поскольку непосредственная специализация шаблонов функций невозможна).

В строке 3 осуществляется пересчет индекса: от размера (количества элементов) кортежа отнимается текущий индекс. Это необходимо для того, чтобы обход кортежа осуществлялся в прямом порядке, от первого элемента к последнему. Если не выполнять пересчет индексов, то обход будет происходить в обратном порядке.

В строке 4 осуществляется вызов объекта. С помощью вызова get по пересчитанному индексу осуществляется доступ к соответствующему элементу кортежа. Для указанного элемента выполняется вызов, на вход ему передается пакет данных callData, распакованный в список аргументов.

В строке 5 происходит рекурсивный вызов. Объявляется структура с новым значением параметра-индекса, уменьшенным на единицу. Вызывается соответствующая функция с передачей кортежа объектов и пакета параметров, и процесс повторяется заново.

С каждой итерацией значение индекса уменьшается, и когда оно станет равным нулю, необходимо остановить итерации, поскольку все элементы кортежа будут посещены. Для этой цели в строке 6 объявлена специализация структуры для нулевого индекса. Тело функции в этой структуре пустое, таким образом, рекурсия будет завершена.

В строке 9 объявлен шаблон распределяющей функции. Этот шаблон имеет два пакета параметров: пакет объектов вызова и пакет данных вызова, типы содержимого пакетов будут выводиться из входных аргументов. В строке 10 объявляется сама функция, которая на вход принимает два аргумента: кортеж объектов вызова и пакет данных вызова.

В строке 11 запускается процесс итерации путем инстанциирования шаблона TupleIterator. Аргументами шаблона выступают: количество объектов вызова (строка 12), вычисляется с помощью операции sizeof применительно к соответствующему пакету параметров; кортеж объектов вызова (строка 13); данные, передаваемые в вызов (строка 14). В строке 15 вызывается стартовая функция итерации с передачей соответствующих аргументов. Как видим, начальное значение индекса равно количеству объектов вызова, которое затем с каждой новой итерацией будет уменьшаться на единицу, в то время как пересчитываемый индекс, соответственно, увеличивается.

5.3.4. Способ 3: объекты и данные в кортежах

При использовании данного способа реализация практически повторяет рассмотренную в предыдущем параграфе, только вместо пакета данных будет использоваться кортеж (Листинг 70).

Листинг 70. Распределение при упаковке объектов и данных в кортежи

template<std::size_t Index, typename CallObjects, typename CallData>          // (1)

struct TupleIterator3

{

  static void IterateTupleItem(CallObjects& callObjects, CallData& callData)  // (2)

struct TupleIterator3

{

  static void IterateTupleItem(CallObjects& callObjects, CallData& callData)  // (2)

  {

      const std::size_t idx = std::tuple_size_v<CallObjects>  Index;         // (3)

      std::apply(std::get<idx>(callObjects), callData);                       // (4)

      TupleIterator3<Index  1, CallObjects, CallData>::IterateTupleItem(callObjects, callData);  // (5)

  }

};


template<typename CallObjects, typename CallData>  // (6)

struct TupleIterator3<0, CallObjects, CallData>    // (7)

{

  static void IterateTupleItem(CallObjects& callObjects, CallData& callData)  // (8)

  {

  }

};


template<typename CallObjects, typename CallData>                                     // (9)

void Distribute3(std::tuple<CallObjects> callObjects, std::tuple<CallData> callData)  // (10)

{

  TupleIterator3               // (11)

  <

  sizeof(CallObjects),      // (12)

  std::tuple<CallObjects>,  // (13)

  std::tuple<CallData>      // (14)

  >

  ::IterateTupleItem(callObjects, callData);  // (15)

}


По сравнению с Листинг 69 п. 5.3.3 изменения здесь следующие. Входными параметрами распределяющей функции (строка 10) являются кортеж объектов и кортеж данных (ранее параметр для данных задавался пакетом). В объявлениях шаблонов структур для обхода кортежа (строки 1, 6) параметр, определяющий данные вызова, объявляется как тип (ранее это был пакет). Вызов объекта (строка 4) осуществляется через std::apply (ранее объект вызывался непосредственно). И еще здесь изменены имена структур, чтобы избежать конфликта имен с предыдущей реализацией.

5.3.5. Сравнение способов

В Листинг 71 приведен пример распределения вызовов с использованием различных способов настройки сигнатуры, в качестве данных выступают два числовых значения.

Листинг 71. Распределение вызовов с заданной сигнатурой

void ExternalHandler(int eventID, int contextID) {}


struct FO

{

void callbackHandler(int eventID, int contextID) {}

void operator() (int eventID, int contextID) {}

};


int main()

{

int eventID = 0, contextID = 1;


FO fo;

auto lambda = [](int eventID, int contextID) {};

auto cb2cl = std::bind(&FO::callbackHandler, fo, _1, _2);


Distribute1(std::tuple(eventID, contextID), ExternalHandler, fo, cb2cl, lambda);

Distribute2(std::tuple(ExternalHandler, fo, cb2cl, lambda), eventID, contextID);

Distribute3(std::tuple(ExternalHandler, fo, cb2cl, lambda), std::tuple(eventID, contextID));

}


С точки зрения эффективности все три способа, в общем-то, равноценны. С точки зрения дизайна можно сказать следующее: первый способ самый простой в реализации; второй способ позволяет легко модифицировать код для сбора дополнительной информации при выполнении вызовов; третий способ позволяет передавать дополнительные параметры в функцию распределения, если это необходимо.

5.3.6. Настройка сигнатуры для перенаправления

В рассмотренных выше примерах мы предполагали, что все получатели используют одну и ту же сигнатуру вызова. Но что делать, если они имеют разные сигнатуры? Нам необходимо разработать какой-то объект, который бы обеспечивал следующее: настройку входной сигнатуры, в которую передаются данные вызова; настройку выходной сигнатуры, которая определяется получателем; преобразование одной сигнатуры в другую. По сути дела, необходимо обеспечить перенаправление вызовов, что решается с помощью инструментов STL, а именно объектов связывания (см. п. 4.6.2). В этом случае в функцию распределителя вместо объекта-получателя передается объект-связывание, который осуществляет перенаправление вызова с заданной сигнатурой. Пример реализации приведен в Листинг 72; здесь в качестве распределяющей функции используется реализация из Листинг 69 п. 5.3.3.

Листинг 72. Перенаправление вызовов с настройкой сигнатуры

void NativeHandler(int eventID)

{

}


void ExternalHandler(int eventID, int contextID)

{

}


struct FO

{

  void operator() (int eventID, int contextID) {}

  void callbackHandler(int eventID, int contextID) {}

};


int main()

{

  int eventID = 0, contextID = 0;

  FO fo;

  auto lambda = [](int eventID, int contextID) {};


  Distribute2(std::tuple(   // (1)

    NativeHandler,          // (2)

    std::bind(ExternalHandler, std::placeholders::_1, contextID),           // (3)

    std::bind(&FO:: callbackHandler, fo, std::placeholders::_1, contextID), // (4)

    std::bind(&FO::operator(), fo, std::placeholders::_1, contextID),       // (5)

Назад Дальше