Обратные вызовы в C++ | страница 20



2.4.3. Исполнитель

Реализация исполнителя приведена в Листинг 17.

Листинг 17. Исполнитель с функциональным объектом

>int main()

>{

>  Initiator initiator;        // (1)

>  CallbackHandler executor;   // (2)

>  initiator.setup(executor);  // (3)

>  initiator.run();            // (4)

>}


В строке 1 объявляется переменная класса-инициатора, в строке 2 объявляется функциональный объект, в строке 3 производится настройка, в строке 4 – запуск.

2.4.4. Синхронный вызов

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

Листинг 18. Инициатор для синхронного вызова с функциональным объектом

>void run(CallbackHandler& callbackObject)

>{

>  int eventID = 0;

>  //Some actions

>  callbackObject(eventID);

>}

2.4.5. Преимущества и недостатки

Преимущества и недостатки реализации обратных вызовов с помощью функционального объекта приведены в Табл. 5.


Табл. 5. Преимущества и недостатки обратных вызовов с помощью функционального объекта


Простая реализация. Самая простая из всех рассмотренных. Необходима только одна переменная – экземпляр класса, весь контекст хранится внутри этого класса. Прозрачный и понятный синтаксис.

Безопасность. При настройке в инициаторе создается копия переданного функционального объекта. Исходный экземпляр становится ненужным, его можно безопасно удалить.

Отсутствие трансляции контекста. Код вызова хранится внутри перегруженного оператора, контекст инкапсулирован внутри класса вместе с кодом.

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

Невозможность реализации API. Следствие монолитной архитектуры: использование API предполагает возможность модификации поведения исполнителя без изменения кода инициатора. Поскольку они оба связаны через единый объект, выполнение указанного требования является нереализуемым.

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