Обратные вызовы в C++ - [40]

Шрифт
Интервал

>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.

Листинг 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 требует, чтобы в качестве предиката использовался именно функциональный объект, мы не можем для этого использовать внешнюю функцию или лямбда-выражение. Это связано с тем, что в контейнере предикат хранится в виде переменной с конструктором, тип переменной определяется параметром шаблона. А наличие конструктора может обеспечить только функциональный объект.

Указанный подход имеет как достоинства, так и недостатки. С одной стороны, нам достаточно всего лишь объявить тип объекта в параметре шаблона, а затем про него можно забыть. Объект не нужно ни настраивать, ни передавать в конструктор как входной аргумент. С другой стороны, было бы удобно использовать в качестве предиката что-либо другое, например, лямбда-выражение или внешнюю функцию. Но в этом случае предикат пришлось бы инициализировать в конструкторе, причем ему нельзя было бы назначить значение по умолчанию. В любом случае, мы вынуждены следовать заданной реализации, поэтому предикат объявляем как функциональный объект.

В STL уже объявлен шаблон структуры для предикатов std::less, параметром которого выступает тип данных, которые необходимо сравнить. Этот предикат принимает на вход две переменные и возвращает


Рекомендуем почитать
Изучаем Java EE 7

Java Enterprise Edition (Java EE) остается одной из ведущих технологий и платформ на основе Java. Данная книга представляет собой логичное пошаговое руководство, в котором подробно описаны многие спецификации и эталонные реализации Java EE 7. Работа с ними продемонстрирована на практических примерах. В этом фундаментальном издании также используется новейшая версия инструмента GlassFish, предназначенного для развертывания и администрирования примеров кода. Книга написана ведущим специалистом по обработке запросов на спецификацию Java EE, членом наблюдательного совета организации Java Community Process (JCP)


Геймдизайн. Рецепты успеха лучших компьютерных игр от Super Mario и Doom до Assassin’s Creed и дальше

Что такое ГЕЙМДИЗАЙН? Это не код, графика или звук. Это не создание персонажей или раскрашивание игрового поля. Геймдизайн – это симулятор мечты, набор правил, благодаря которым игра оживает. Как создать игру, которую полюбят, от которой не смогут оторваться? Знаменитый геймдизайнер Тайнан Сильвестр на примере кейсов из самых популярных игр рассказывает как объединить эмоции и впечатления, игровую механику и мотивацию игроков. Познакомитесь с принципами дизайна, которыми пользуются ведущие студии мира! Создайте игровую механику, вызывающую эмоции и обеспечивающую разнообразие.


Обработка событий в С++

В книге рассказывается история главного героя, который сталкивается с различными проблемами и препятствиями на протяжении всего своего путешествия. По пути он встречает множество второстепенных персонажей, которые играют важные роли в истории. Благодаря опыту главного героя книга исследует такие темы, как любовь, потеря, надежда и стойкость. По мере того, как главный герой преодолевает свои трудности, он усваивает ценные уроки жизни и растет как личность.


MFC и OpenGL

В книге рассказывается история главного героя, который сталкивается с различными проблемами и препятствиями на протяжении всего своего путешествия. По пути он встречает множество второстепенных персонажей, которые играют важные роли в истории. Благодаря опыту главного героя книга исследует такие темы, как любовь, потеря, надежда и стойкость. По мере того, как главный герой преодолевает свои трудности, он усваивает ценные уроки жизни и растет как личность.


Симуляция частичной специализации

В книге рассказывается история главного героя, который сталкивается с различными проблемами и препятствиями на протяжении всего своего путешествия. По пути он встречает множество второстепенных персонажей, которые играют важные роли в истории. Благодаря опыту главного героя книга исследует такие темы, как любовь, потеря, надежда и стойкость. По мере того, как главный герой преодолевает свои трудности, он усваивает ценные уроки жизни и растет как личность.


Питон — модули, пакеты, классы, экземпляры

Python - объектно-ориентированный язык сверхвысокого уровня. Python, в отличии от Java, не требует исключительно объектной ориентированности, но классы в Python так просто изучить и так удобно использовать, что даже новые и неискушенные пользователи быстро переходят на ОО-подход.