Гибкость. Управлять контекстом можно тремя способами, подобные возможности отсутствуют в других реализациях.
Отсутствие трансляции контекста. Контекст транслировать не нужно, метод-член имеет полный доступ к содержимому класса.
Сложность. Код получается довольно громоздким и запутанным.
Тип класса должен объявляться в инициаторе. Здесь достаточно только предварительного объявления класса. Полное объявление класса в инициаторе делать необязательно и даже нежелательно, потому что логически это обработчик обратного вызова, то есть он относится к исполнителю и должен быть в нем реализован. Тем не менее, требование предварительного объявления класса ограничивает независимость исполнителя: он может использовать только те типы классов, которые были предварительно объявлены в инициаторе.
Инициатор должен хранить указатель на метод и указатель на класс. Увеличивается расход памяти.
2.4. Функциональный объект
С точки зрения C++ функциональный объект – это класс, который имеет перегруженный оператор вызова функции7.
Графическое изображение обратного вызова с помощью функционального объекта представлено на Рис. 14. Исполнитель реализуется в виде класса, код упаковывается в перегруженный оператор вызовы функции, в качестве контекста выступает экземпляр класса. При настройке экземпляр класса как аргумент сохраняется в инициаторе8. Инициатор осуществляет обратный вызов посредством вызова перегруженного оператора, передавая ему требуемую информацию. Контекст здесь передавать не нужно, поскольку внутри оператора доступно все содержимое класса.
Рис. 14. Реализация обратного вызова с помощью функционального объекта.
Предварительно необходимо объявить функциональный объект (см. Листинг 15), потому что его объявление должен видеть как инициатор, так и исполнитель.
Листинг 15.Объявление функционального объекта
>class CallbackHandler
>{
>public:
> void operator() (int eventID) //This is an overloaded operator
> {
> //It will be called by server
> };
>};
Реализация инициатора приведена в Листинг 16.
Листинг 16. Инициатор с функциональным объектом
>class Initiator // (1)
>{
>public:
> void setup(const CallbackHandler& callback) // (2)
> {
> callbackObject = callback;
> }
> void run() // (3)
> {
> int eventID = 0;
> //Some actions
> callbackObject(eventID); // (4)
> }
>private:
> CallbackHandler callbackObject; // (5)
>};
В строке 1 мы объявляется класс-инициатор. В строке 2 объявляется функция для настройки вызова, в которую передается ссылка на функциональный объект. Данный объект присваивается переменной-аргументу, объявленному в строке 5. В строке 3 объявлена функция запуска, внутри этой функции в строке 4 производится вызов перегруженного оператора. Как видим, синтаксис вызова перегруженного оператора совпадает с синтаксисом вызова обычной функции.
Реализация исполнителя приведена в Листинг 17.
Листинг 17. Исполнитель с функциональным объектом
>int main()
>{
> Initiator initiator; // (1)
> CallbackHandler executor; // (2)
> initiator.setup(executor); // (3)
> initiator.run(); // (4)
>}
В строке 1 объявляется переменная класса-инициатора, в строке 2 объявляется функциональный объект, в строке 3 производится настройка, в строке 4 – запуск.
Реализация инициатора для синхронного вызова представлена в Листинг 18. В отличие от асинхронного вызова, здесь функциональный объект не сохраняется как аргумент, он передается через входные параметры функции.
Листинг 18. Инициатор для синхронного вызова с функциональным объектом
>void run(CallbackHandler& callbackObject)
>{
> int eventID = 0;
> //Some actions
> callbackObject(eventID);
>}
2.4.5. Преимущества и недостатки
Преимущества и недостатки реализации обратных вызовов с помощью функционального объекта приведены в Табл. 5.
Табл. 5. Преимущества и недостатки обратных вызовов с помощью функционального объекта
Простая реализация. Самая простая из всех рассмотренных. Необходима только одна переменная – экземпляр класса, весь контекст хранится внутри этого класса. Прозрачный и понятный синтаксис.