Обратные вызовы в C++ | страница 20
2.4.3. Исполнитель
Реализация исполнителя приведена в Листинг 17.
>int main()
>{
> Initiator initiator; // (1)
> CallbackHandler executor; // (2)
> initiator.setup(executor); // (3)
> initiator.run(); // (4)
>}
В строке 1 объявляется переменная класса-инициатора, в строке 2 объявляется функциональный объект, в строке 3 производится настройка, в строке 4 – запуск.
2.4.4. Синхронный вызов
Реализация инициатора для синхронного вызова представлена в Листинг 18. В отличие от асинхронного вызова, здесь функциональный объект не сохраняется как аргумент, он передается через входные параметры функции.
>void run(CallbackHandler& callbackObject)
>{
> int eventID = 0;
> //Some actions
> callbackObject(eventID);
>}
2.4.5. Преимущества и недостатки
Преимущества и недостатки реализации обратных вызовов с помощью функционального объекта приведены в Табл. 5.
Табл. 5. Преимущества и недостатки обратных вызовов с помощью функционального объекта
Простая реализация. Самая простая из всех рассмотренных. Необходима только одна переменная – экземпляр класса, весь контекст хранится внутри этого класса. Прозрачный и понятный синтаксис.
Безопасность. При настройке в инициаторе создается копия переданного функционального объекта. Исходный экземпляр становится ненужным, его можно безопасно удалить.
Отсутствие трансляции контекста. Код вызова хранится внутри перегруженного оператора, контекст инкапсулирован внутри класса вместе с кодом.
Общий функциональный объект. Инициатор и исполнитель связаны через единый функциональный объект, они оба должны видеть его объявление. Вся логика обработки реализуется внутри объекта. Это приводит к монолитной архитектуре, что сильно затрудняет модификацию поведения обработчика. По сути дела, исполнитель встраивается в инициатор и становится его составной частью9.
Невозможность реализации API. Следствие монолитной архитектуры: использование API предполагает возможность модификации поведения исполнителя без изменения кода инициатора. Поскольку они оба связаны через единый объект, выполнение указанного требования является нереализуемым.
Высокое быстродействие. А вот здесь недостатки монолитной архитектуры превращаются в достоинства. Дело в том, что поскольку инициатор сохраняет у себя объект, он имеет доступ к коду перегруженного оператора, т. е. к коду обработчика вызова. Как следствие, оптимизирующий компилятор получает возможность встроить код обработчика непосредственно в точку вызова, опуская вызов функции (перегруженный оператор тоже является функцией), что значительно ускоряет выполнение вызова. Рассмотрим этот момент подробнее.