void operator ()(ArgumentList… arguments) // (4)
{
for (auto& callObject : callObjects)
{
callObject(arguments…);
}
}
private:
std::list< std::function
};
В строке 1 объявлена общая специализация шаблона. Реализация класса здесь отсутствует, поскольку для каждой сигнатуры она будет различной. В строке 2 объявлен шаблон для частичной специализации, в котором два аргумента: тип возвращаемого значения и пакет параметров, передаваемых на вход вызова. Подобную конструкцию мы использовали, когда рассматривали настройку сигнатуры для универсального аргумента (п. 4.5.2).
В строке 3 объявлен метод, который добавляет объект вызова в контейнер, сам контейнер объявлен в строке 5. Тип контейнера мы выбираем список, поскольку он не перемещает элементов при вставке/удалении, а произвольный доступ здесь не требуется. Типом хранимых данных в контейнере является объект std::function, аргументы которого задаются исходя из параметров в объявлении шаблона класса.
В строке 4 объявлен перегруженный оператор, который осуществляет распределение вызовов, т. е. является распределяющей функцией. Он обходит элементы контейнера и осуществляет вызов в соответствии с списком аргументов, типы которых задаются в пакете параметров шаблона.
5.6.2. Получение возвращаемых значений
Как получить возвращаемые значения для динамического набора? На момент вызова распределяющей функции количество получателей может быть любым, и, соответственно, число возвращаемых значений заранее не определено. Использовать динамический контейнер как возвращаемое значение функции является плохой идеей: во-первых, заполнение контейнера и создание его копии в стеке требует значительного расхода времени и увеличивает фрагментацию памяти; во-вторых, если возвращаемое значение не используется, то все вышеописанное будет работать «вхолостую», выполняя совершенно ненужные операции. Использовать контейнер как входной параметр – это тоже идея не очень: мы вынуждаем привязаться к контейнеру определенного типа, а если нам результаты нужно хранить в других структурах? А если нам вообще их не нужно хранить, а нужно всего лишь проверить? Вопросы, вопросы… Можно предложить следующее решение: для возврата результата использовать обратный вызов, а пользователь сам решает, что делать с возвращаемыми значениями. Реализация приведена в Листинг 82.
template
class DynamicDistributor
{
/**********************……**********************************/
template
void operator()(CallbackReturn callbackReturn, ArgumentList… arguments)
{
for (auto& callObject : callObjects)
{
callbackReturn(callObject(arguments…)); // (2)
}
}
private:
std::list< std::function
};
Реализация совпадает с Листинг 82 п. 5.6.1, только добавляется еще один перегруженный оператор. Его шаблон объявлен строке 1, параметром шаблона является тип аргумента, через который будет выполняться обратный вызов. В строке 2 происходит вызов объекта, результат возвращается через аргумент, переданный как входной параметр функции.
Пример распределения вызовов для динамического набора получателей приведен в Листинг 83.
struct FO
{
int operator() (int eventID) { return 10; }
int callbackHandler(int eventID) { return 100; }
};
int ExternalHandler(int eventID)
{
return 0;
}
int main()
{
int eventID = 0;
FO fo;
auto lambda = [](int eventID) { return 0; };
auto binding = std::bind(&FO::callbackHandler, fo, std::placeholders::_1);
DynamicDistributor
distributor.addCallObject(fo); // (2)
distributor.addCallObject(ExternalHandler); // (3)
distributor.addCallObject(binding); // (4)
distributor.addCallObject(lambda); // (5)