Виталий Ткаченко - Обратные вызовы в C++
- Название:Обратные вызовы в C++
- Автор:
- Жанр:
- Издательство:Array SelfPub.ru
- Год:2021
- ISBN:нет данных
- Рейтинг:
- Избранное:Добавить в избранное
-
Отзывы:
-
Ваша оценка:
Виталий Ткаченко - Обратные вызовы в C++ краткое содержание
Обратные вызовы в C++ - читать онлайн бесплатно полную версию (весь текст целиком)
Интервал:
Закладка:
5.7. Адресное распределение
5.7.1. Понятие адресного распределения
До сих пор мы предполагали, что вызовы должны быть сделаны для всех получателей. Однако зачастую требуется распределять вызовы не всем, а только некоторым получателям из списка.
Как это реализовать? Прежде всего, необходимо как-то идентифицировать получателей, для чего вводится понятие адреса. Каждому получателю присваивается адрес, и с каждым адресом связывается универсальный аргумент, который хранит объект вызова. Таким образом, зная адреса получателей, можно осуществлять вызовы только для конкретных объектов. Попутно решается задача изменения списка получателей: по заданному адресу возможно удаление/изменение соответствующего аргумента.
Что может быть адресом? Все что угодно: числа, строки, структуры и т. п. Единственное требование, предъявляемое к адресу, заключается в том, что он должен быть уникальным, в противном случае невозможно однозначно идентифицировать получателя. Мы сделаем тип адреса параметром шаблона, а пользователь сам решит, что использовать в качестве адреса.
Теперь в функцию распределителя, помимо данных, будет передаваться адрес. Источник должен найти аргумент, которому соответствует полученный адрес, и выполнить для него вызов. Для поиска необходимо сравнивать адреса, но ведь мы не знаем их типы: теперь это параметр шаблона, и тип используемого адреса станет известен только после инстанциирования. По этой причине мы не можем производить сравнение адресов напрямую, для этого необходимо использовать предикаты (см. п. 4.3.3).
Какой выбрать контейнер? На эту роль лучше других подойдет std::map. Во-первых, не нужно вводить новую структуру для хранения адреса и аргумента, контейнер реализует ее естественным образом в виде пары «ключ-значение». И, во-вторых, std::mapосуществляет быстрый поиск по ключу, в качестве которого выступает адрес. Структурная схема изображена на Рис. 25.

Рис. 25. Структурная схема адресного распределения
5.7.2. Адресный распределитель
Реализация адресного распределителя приведена в Листинг 84.
template class AddressDistributor; // (1)
template // (2)
class AddressDistributor
// (3)
{
public:
template // (4)
void addReceiver(Address address, CallObject object)
{
callObjects.insert({ address,object } );
}
void deleteReceiver(Address address) // (5)
{
callObjects.erase(address);
}
Return operator()(Address address, ArgumentList… arguments) // (6)
{
auto iterator = callObjects.find(address); // (7)
if (iterator != callObjects.end())
{
return iterator->second(arguments…); // (8)
}
else
{
throw std::invalid_argument("Invalid receiver address"); // (9)
}
}
private:
std::map< Address, std::function, AddressCompare > callObjects; // (10)
};
В строке 1 объявлена общая специализация шаблона, параметрами выступают адрес получателя Address, предикат для сравнения AddressCompareи сигнатура распределяющей функции Function. Реализация здесь отсутствует, поскольку для каждой сигнатуры требуется отдельная специализация – аналогично настройке сигнатуры для универсального аргумента (п. 4.5.2).
В строке 2 объявлена частичная специализация, в которой дополнительно представлены параметр для возвращаемого значения Returnи пакет параметров ArgumentListдля аргументов функции. В строке 3 объявлен класс, который специализируется сигнатурой из указанных параметров.
В строке 4 объявлен шаблон метода для добавления получателя, который принимает адрес address, вызываемый объект objectи добавляет их в контейнер. В строке 5 объявлен метод для удаления получателя. Оба метода работают с контейнером, который объявлен в строке 10. Контейнер объявлен как std::map, ключом является адрес, а значением – объект std::functionс заданной сигнатурой.
В строке 6 объявлен перегруженный оператор, который осуществляет распределение вызовов, т. е. является распределяющей функцией. Он пробегает по всем элементам контейнера и осуществляет вызов в соответствии с списком аргументов, типы которых задаются в пакете параметров шаблона класса. Поскольку мы используем адресное распределение, т. е. предполагается, что вызов попадает только одному получателю, то мы операторе можем вернуть результат вызова.
В строке 7 происходит поиск получателя по адресу. Если получатель найден, то происходит вызов объекта (строка 8). Если получатель не найден, то генерируется исключение (строка 9), иначе какой результат нам возвратить?
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 // (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, 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требует, чтобы в качестве предиката использовался именно функциональный объект, мы не можем для этого использовать внешнюю функцию или лямбда-выражение. Это связано с тем, что в контейнере предикат хранится в виде переменной с конструктором, тип переменной определяется параметром шаблона. А наличие конструктора может обеспечить только функциональный объект.
Читать дальшеИнтервал:
Закладка: