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

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

>{

>  int eventID = 0;

>  ptrCallback(eventID, contextData);

>}


>void run(ptr_callback_static ptrCallback, Executor* contextData = nullptr)  // (2)

>{

>  int eventID = 0;

>  ptrCallback(eventID, contextData);

>}


>void run(Executor* ptrClientCallbackClass, ptr_callback_method ptrClientCallbackMethod)  // (3)

>{

>  int eventID = 0;

>  (ptrClientCallbackClass->*ptrClientCallbackMethod)(eventID);

>}


>void run(Executor callbackHandler)  // (4)

>{

>  int eventID = 0;

>  callbackHandler(eventID);

>}


Можно заметить, что все реализации, по сути, одинаковы, отличаются только типы и количество входных аргументов. Поэтому, можно попытаться сделать шаблон. Возьмем наиболее простой случай, когда функция на вход принимает только один параметр (Листинг 25):

Листинг 25. Шаблон для инициатора синхронного вызова

>template 

>void run(CallbackArgument callbackHandler)

>{

>  int eventID = 0;

>  //Some actions

>  callbackHandler(eventID);

>}


Получившийся шаблон подходит для реализации вызовов с помощью функциональных объектов (в Листинг 25 это строка номер 4), а также для лямбда-выражений. В последнем случае в качестве типа аргумента будет подставлен тип лямбда-выражения, определяемый компилятором.

Что же нам делать для остальных реализаций? Для указателей на функцию и указателей на статический метод (строки 1 и 2) можно сделать отдельный шаблон с двумя параметрами (Листинг 26):

Листинг 26. Шаблон для инициатора с двумя параметрами

>template 

>void run(CallbackArgument callbackHandler, Context* context)

>{

>  int eventID = 0;

>  //Some actions

>  callbackHandler(eventID, context);

>}


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

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

4.2.2. Преобразование вызовов

Для преобразования вызовов используется функциональный объект, в котором хранятся данные, необходимые для осуществления обратного вызова. Объявляется перегруженный оператор, который принимает информацию вызова. Реализация оператора выполняет требуемый вызов, передавая ему на вход полученную информацию вызова и, дополнительно, хранимые данные18.

Вначале рассмотрим вызовы через указатели на функцию. Создадим шаблон для функционального объекта, в котором будем хранить указатель на функцию и контекст. Перегрузим оператор вызова функции, в реализации которого по хранимому указателю вызовем функцию-обработчик и передадим ей хранимый контекст (Листинг 27).

Листинг 27. Функциональный объект для вызова функции с передачей контекста

>template  // (1)

>class CallbackConverter  // (2)

>{

>public:

>  CallbackConverter (Function argFunction = nullptr, Context argContext = nullptr)  // (3)

>  {

>    ptrFunction = argFunction; context = argContext;

>  }


>  void operator() (int eventID)      // (4)

>  {

>     ptrFunction(eventID, context);  // (5)

>  }

>private:

>  Function ptrFunction;  // (6)

>  Context context;       // (7)

>};


В строке 1 объявлен шаблон с двумя параметрами – тип указателя на функцию и тип для контекста. В строке 2 объявлено имя класса. В строке 3 объявлен конструктор, который будет сохранять требуемые значения – указатель на функцию и указатель на контекст, переменные для хранения объявлены в строках 6 и 7. В строке 4 осуществляется перегрузка оператора вызова функции, который делает обратный вызов, передавая информацию и сохраненный контекст.

Рассмотренный шаблон также будет работать для указателей на статический метод класса, только необходимо объявить соответствующие типы указателей.

Для указателей на метод-член класса сделаем специализацию шаблона, как это показано в Листинг 28.

Листинг 28. Функциональный объект для вызова метода класса

>template  // (1)

>class CallbackConverter   // (2)

>{

>public:

>  using ClassMethod = void(ClassName::*)(int);  // (3)


>  CallbackConverter(ClassMethod methodPointer = nullptr, ClassName* classPointer = nullptr)  // (4)

>  {

>    ptrClass = classPointer; ptrMethod = methodPointer;

>  }


>  void operator()(int eventID)       // (5)

>  {

>    ptrClass->*ptrMethod)(eventID);  // (6)

>  }

>private:

>  ClassName* ptrClass;    // (7)

>  ClassMethod ptrMethod;  // (8)

>};


В строке 1 объявлен шаблон с параметром – именем класса. В строке 2 объявлена специализация шаблона из Листинг 27. Именно эта специализация будет выбрана компилятором, если шаблон инстанциируется указателем на метод класса и указателем на класс. В строке 3 объявлен тип – указатель на метод класса. Этот тип выводится из имени класса, поэтому в шаблоне одного параметра – имени класса – будет достаточно. В строке 4 объявляется конструктор, который будет сохранять требуемые значения – указатель на экземпляр класса и указатель на метод, переменные для хранения объявлены в строках 7 и 8. В строке 5 перегружается оператор вызова функции, который вызывает метод класса.


Рекомендуем почитать
Pro Git

Разработчику часто требуется много сторонних инструментов, чтобы создавать и поддерживать проект. Система Git — один из таких инструментов и используется для контроля промежуточных версий вашего приложения, позволяя вам исправлять ошибки, откатывать к старой версии, разрабатывать проект в команде и сливать его потом. В книге вы узнаете об основах работы с Git: установка, ключевые команды, gitHub и многое другое.В книге рассматриваются следующие темы:основы Git;ветвление в Git;Git на сервере;распределённый Git;GitHub;инструменты Git;настройка Git;Git и другие системы контроля версий.


Java 7

Рассмотрено все необходимое для разработки, компиляции, отладки и запуска приложений Java. Изложены практические приемы использования как традиционных, так и новейших конструкций объектно-ориентированного языка Java, графической библиотеки классов Swing, расширенной библиотеки Java 2D, работа со звуком, печать, способы русификации программ. Приведено полное описание нововведений Java SE 7: двоичная запись чисел, строковые варианты разветвлений, "ромбовидный оператор", NIO2, новые средства многопоточности и др.


MFC и OpenGL

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


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

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


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

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


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

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