Виталий Ткаченко - Обратные вызовы в C++
- Название:Обратные вызовы в C++
- Автор:
- Жанр:
- Издательство:Array SelfPub.ru
- Год:2021
- ISBN:нет данных
- Рейтинг:
- Избранное:Добавить в избранное
-
Отзывы:
-
Ваша оценка:
Виталий Ткаченко - Обратные вызовы в C++ краткое содержание
Обратные вызовы в C++ - читать онлайн бесплатно полную версию (весь текст целиком)
Интервал:
Закладка:
В строке 9 объявлен шаблон распределяющей функции. Этот шаблон имеет два пакета параметров: пакет объектов вызова и пакет данных вызова, типы содержимого пакетов будут выводиться из входных аргументов. В строке 10 объявляется сама функция, которая на вход принимает два аргумента: кортеж объектов вызова и пакет данных вызова.
В строке 11 запускается процесс итерации путем инстанциирования шаблона TupleIterator. Аргументами шаблона выступают: количество объектов вызова (строка 12), вычисляется с помощью операции sizeofприменительно к соответствующему пакету параметров; кортеж объектов вызова (строка 13); данные, передаваемые в вызов (строка 14). В строке 15 вызывается стартовая функция итерации с передачей соответствующих аргументов. Как видим, начальное значение индекса равно количеству объектов вызова, которое затем с каждой новой итерацией будет уменьшаться на единицу, в то время как пересчитываемый индекс, соответственно, увеличивается.
5.3.4. Способ 3: объекты и данные в кортежах
При использовании данного способа реализация практически повторяет рассмотренную в предыдущем параграфе, только вместо пакета данных будет использоваться кортеж (Листинг 70).
template // (1)
struct TupleIterator3
{
static void IterateTupleItem(CallObjects& callObjects, CallData& callData) // (2)
{
const std::size_t idx = std::tuple_size_v – Index; // (3)
std::apply(std::get(callObjects), callData); // (4)
TupleIterator3::IterateTupleItem(callObjects, callData); // (5)
}
};
template // (6)
struct TupleIterator3<0, CallObjects, CallData> // (7)
{
static void IterateTupleItem(CallObjects& callObjects, CallData& callData) // (8)
{
}
};
template // (9)
void Distribute3(std::tuple callObjects, std::tuple callData) // (10)
{
TupleIterator3 // (11)
<
sizeof…(CallObjects), // (12)
std::tuple, // (13)
std::tuple // (14)
>
::IterateTupleItem(callObjects, callData); // (15)
}
По сравнению с Листинг 69 п. 5.3.3 изменения здесь следующие. Входными параметрами распределяющей функции (строка 10) являются кортеж объектов и кортеж данных (ранее параметр для данных задавался пакетом). В объявлениях шаблонов структур для обхода кортежа (строки 1, 6) параметр, определяющий данные вызова, объявляется как тип (ранее это был пакет). Вызов объекта (строка 4) осуществляется через std::apply(ранее объект вызывался непосредственно). И еще здесь изменены имена структур, чтобы избежать конфликта имен с предыдущей реализацией.
5.3.5. Сравнение способов
В Листинг 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.
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)
std::bind(lambda, std::placeholders::_1, contextID) // (6)
),
eventID // (7)
);
}
Входными аргументами распределяющей функции служат кортеж объектов вызова (объявлен в строке 1) и данные вызова (строка 7). В строке 2 в кортеж передается объект вызова с сигнатурой, совпадающей с исходной. В строке 3 передается объект связывания (результат вызова std::bind), в котором исходный вызов перенаправляется в назначенную функцию ExternalHandler. В строке 4 объект связывания перенаправляет вызов в метод-член структуры, в строке 5 – в перегруженный оператор, в строке 6 – в лямбда-выражение.
5.4. Возврат результатов выполнения
5.4.1. Получение возвращаемых значений
До сих пор мы считали, что функции, реализующие код вызова, не возвращают результатов. Однако в некоторых случаях необходимо получить результаты выполнения вызовов. Очевидно, что в этом случае их должна вернуть распределяющая функция. Как же сформировать возвращаемые значение?
Поскольку возвращаемые значения могут иметь различные типы, напрашивается сохранять их в кортеже, который затем будет возвращаться как результат работы распределяющей функции. Но мы же не знаем заранее типы возвращаемых значений, их определяют объекты вызова. Какие тогда типы задавать при инстанциировании переменной-кортежа? Можно предложить следующее решение: при объявлении кортежа не указывать явно хранимые в нем типы, а в конструктор в качестве входных аргументов передать результаты выполнения вызовов. В этом случае типы элементов кортежа будут выведены автоматически.
Читать дальшеИнтервал:
Закладка: