Виталий Ткаченко - Обратные вызовы в C++
- Название:Обратные вызовы в C++
- Автор:
- Жанр:
- Издательство:Array SelfPub.ru
- Год:2021
- ISBN:нет данных
- Рейтинг:
- Избранное:Добавить в избранное
-
Отзывы:
-
Ваша оценка:
Виталий Ткаченко - Обратные вызовы в C++ краткое содержание
Обратные вызовы в C++ - читать онлайн бесплатно полную версию (весь текст целиком)
Интервал:
Закладка:
Итак, рассмотрим, как реализуется распределение вызовов.
5.2. Статический набор получателей
5.2.1. Распределение в статическом наборе
Если типы и количество получателей известны на этапе компиляции и не планируется их изменение в процессе выполнения программы, то мы имеем статический набор получателей. В этом случае распределитель можно реализовать в виде шаблонной функции, которая в качестве входных аргументов будет принимать объекты вызова. Но поскольку типы объектов и их количество могут быть различными, логично в качестве входного параметра функции использовать пакет, задаваемый шаблоном.
Итак, нам необходимо выполнить вызов для каждого объекта, входящего в пакет. Для решения этой задачи используется техника рекурсивного развертывания пакета, суть которой заключается в следующем.
Объявляется функция, первым параметром которой выступает объект вызова, а вторым – пакет. Когда на вход данной функции поступает пакет, первый объект из него извлекается, происходит вызов этого объекта, а затем функция рекурсивно вызывается вновь с пакетом, содержащим еще не извлеченные объекты. Когда в результате рекурсивных вызовов все объекты будут извлечены, будет вызвана функция, на вход которой будет передан пустой пакет. Данная функция завершает рекурсивное выполнение.
Реализация описанной техники приведена в Листинг 63.
void Call() // (1)
{
}
template < typename First, typename…Others>
void Call(First& first, Others…rest) // (2)
{
first(); // (3)
Call(rest…); // (4)
}
template
void Distribute(CallObjects… objects) // (5)
{
Call(objects…); // (6)
}
Графически развертывание пакета параметров для трех аргументов изображено на Рис. 22. Процесс начинается с вызова распределяющей функции, которая объявлена в строке 5. Здесь используется пакет параметров objects, который содержит объекты вызова. Внутри этой функции, в строке 6, происходит первый вызов рекурсивной функции, которой на вход передаются соответствующий аргумент в виде пакета.
Рекурсивная функция Callобъявлена в строке 2. Эта функция принимает два аргумента: первый параметр из пакета firstи пакет остальных параметров rest. При первом вызове пакет параметров из Distributeпередается в эту функцию, и там происходит его распаковка: первый параметр извлекается и помещается в first, оставшаяся часть пакета записывается в rest. В строке 3 производится вызов, а пакет с оставшимися параметрами передается в рекурсивный вызов Call(строка 4).
Итак, на каждом шаге рекурсивного вызова из пакета извлекается очередной параметр, а размер исходного пакета уменьшается. Таким образом, в итоге все параметры будут извлечены, и пакет станет пустым. Эта ситуация обрабатывается путем объявления функции с пустым пакетом параметров, т. е. функции, которая на вход не принимает ни одного аргумента (строка 1). Тело этой функции пустое, в ней происходит возврат управления, и по цепочке рекурсивных вызовов управление возвращается в исходную точку в строке 6.

Рис. 22. Рекурсивное развертывание пакета параметров для трех аргументов
Использование распределения вызовов для статического набора получателей приведено в Листинг 64.
void ExternalHandler() // (1)
{
}
struct FO
{
void callbackHandler() {}
void operator() () {}
};
int main()
{
FO fo; // (2)
auto lambda = []() {}; // (3)
auto cb2cl = std::bind(&FO::callbackHandler, fo); // (4)
Distribute(ExternalHandler, fo, cb2cl, lambda); // (5)
}
В строках 1, 2, 3, 4 объявлены соответствующие объекты вызова: внешняя функция, функциональный объект, лямбда-выражение, объект для вызова метода класса. Для вызова метода класса в строке 4 объявляется объект связывания (см. п. 4.6.6), в строке 5 происходит распределение вызовов.
5.2.2. Передача данных
Если в вызов необходимо передавать данные, то для этого в описанные выше функции необходимо ввести дополнительный параметр (Листинг 65).
template // (1)
void Call(CallData& data)
{
}
template // (2)
void Call(CallData data, First& first, Others&…rest)
{
first(data); // (3)
Call(data, rest…); // (4)
}
template // (5)
void Distribute(CallData data, CallObjects… objects)
{
Call(data, objects…); // (6)
}
Приведенная реализация повторяет Листинг 63 п. 5.2.1, только теперь в функциях к объектам вызова добавляется параметр dataдля передачи данных.
Пример распределения для статического набора получателей с передачей данных представлен в Листинг 66.
void ExternalHandler(int eventID) // (1)
{
}
struct FO
{
void callbackHandler(int eventID) {}
void operator() (int eventID) {}
};
int main()
{
using namespace std::placeholders;
FO fo; // (2)
auto lambda = [](int eventID) {}; // (3)
auto cb2cl = std::bind(&FO::callbackHandler, fo, _1); // (4)
int eventID = 0; // (5)
Distribute(eventID, ExternalHandler, fo, cb2cl, lambda); // (6)
}
В строках 1, 2, 3, 4 объявлены соответствующие объекты вызова: внешняя функция, функциональный объект, лямбда-выражение, объект для вызова метода класса. Для вызова метода класса в строке 4 объявляется объект связывания (см. п. 4.6.6), в строке 5 объявляется переменная для передачи данных. В строке 6 происходит распределение вызовов, первым параметром передается аргумент данных eventID.
5.3. Настройка сигнатуры для передачи данных
5.3.1. Общая концепция
В рассмотренной выше реализации распределения с передачей данных (п. 5.2.2) есть один недостаток: данные, передаваемые в вызов, имеют заранее прописанную сигнатуру. В нашем случае предполагается, что это единственная числовая переменная. Если нам понадобится другая сигнатура, т. е. другой набор и типы переменных, нам придется повторять всю реализацию распределения, изменяя только сам вызов. Можно ли настроить сигнатуру, как это мы делали в универсальном аргументе? Тогда мы определяли сигнатуру с помощью пакета параметров, но теперь у нас пакет параметров используется для задания объектов вызова.
Получается, нам необходим еще один пакет параметров. В общем случае допускается объявлять шаблон функции с несколькими пакетами 30 30 Но не шаблон класса, в шаблонах классов пакет параметров может быть только один. Кроме того, если в шаблоне объявляется пакет параметров, он должен быть последним в списке параметров шаблона.
, однако в этом случае для вывода типов пакета используется схема раскрытия. По этой причине необходимо, чтобы все пакеты параметров раскрывались параллельно в рамках одной синтаксической конструкции (Листинг 67), что для нашей задачи не подходит: мы должны вначале раскрыть пакет объектов вызова, а затем для каждого элемента пакета раскрыть пакет сигнатуры. Здесь нужно какое-то другое решение.
Интервал:
Закладка: