5.7.3. Использование адресного распределения
Пример использования адресного распределения приведен в Листинг 85.
struct FO
{
int operator() (int eventID)
{
return 10;
}
};
int ExternalHandler(int eventID)
{
return 0;
}
struct ReceiverAddress // (1)
{
ReceiverAddress(int idGroup = 0, int idNumber = 0)
{
group = idGroup; number = idNumber;
}
int group;
int number;
};
template<>
struct std::less<ReceiverAddress> // (2)
{
bool operator() (const ReceiverAddress& addr1, const ReceiverAddress& addr2) const
{
if (addr1.group < addr2.group)
{
return true;
}
else
{
if (addr1.group == addr2.group)
return addr1.number < addr2.number;
else
return false;
}
}
};
int main()
{
int eventID = 0;
FO fo;
auto lambda = [](int eventID) { return 0; };
AddressDistributor<ReceiverAddress, std::less<ReceiverAddress>, int(int)> distributor; // (3)
distributor.addReceiver({ 1,1 }, fo); // (4)
distributor.addReceiver({ 2,2 }, ExternalHandler); // (5)
distributor.addReceiver({ 3,3 }, lambda); // (6)
distributor({ 1,1 }, eventID); // (7)
distributor({ 2,2 }, eventID); // (8)
distributor({ 3,3 }, eventID); // (9)
}
В строке 1 объявлена структура для адреса, которая состоит из двух полей: идентификатор группы и номер получателя в группе. Сравнить эти две структуры напрямую нельзя, поэтому потребуется реализовать предикат.
В строке 2 объявлен функциональный объект, реализующий предикат для сравнения адресов. Почему именно в таком виде? Дело в том, что std::map требует, чтобы в качестве предиката использовался именно функциональный объект, мы не можем для этого использовать внешнюю функцию или лямбда-выражение. Это связано с тем, что в контейнере предикат хранится в виде переменной с конструктором, тип переменной определяется параметром шаблона. А наличие конструктора может обеспечить только функциональный объект.
Указанный подход имеет как достоинства, так и недостатки. С одной стороны, нам достаточно всего лишь объявить тип объекта в параметре шаблона, а затем про него можно забыть. Объект не нужно ни настраивать, ни передавать в конструктор как входной аргумент. С другой стороны, было бы удобно использовать в качестве предиката что-либо другое, например, лямбда-выражение или внешнюю функцию. Но в этом случае предикат пришлось бы инициализировать в конструкторе, причем ему нельзя было бы назначить значение по умолчанию. В любом случае, мы вынуждены следовать заданной реализации, поэтому предикат объявляем как функциональный объект.
В STL уже объявлен шаблон структуры для предикатов std::less, параметром которого выступает тип данных, которые необходимо сравнить. Этот предикат принимает на вход две переменные и возвращает true, если первая меньше второй34. std::less реализует арифметическое сравнение, поэтому для типов, которые поддерживают арифметические операции, предикат объявлять не нужно, он будет сгенерирован компилятором. Однако в нашем случае данные арифметически сравниваться не могут, поэтому мы специализируем этот шаблон своим типом (строка 2) и реализуем перегруженный оператор, который будет сравнивать две структуры. При инстанциировании контейнера компилятор сам выберет подходящую специализацию предиката, исходя из типа хранимых элементов.
В строке 3 объявлен объект распределителя путем инстанциирования соответствующего шаблона. Аргументами шаблона выступают тип адреса, предикат для сравнения и сигнатура для вызова объектов. В строках 4, 5, 6 в распределитель добавляются объекты вызова различных типов, в строках 7, 8, 9 эти объекты будут вызваны в соответствии с их адресами.
5.8. Итоги
Под распределением вызовов понимается техника, в которой при вызове единственной функции осуществляется выполнение множества вызовов через соответствующие аргументы. Структурно распределение состоит из следующих компонентов: источник, получатель, распределитель, распределяющая функция.
34
Контейнер std::map требует именно такой предикат, less, который возвращает истину в случае, если первый элемент меньше второго. Другие контейнеры могут требовать иные предикаты, например, проверку на равенство equal.