Обратные вызовы в C++ | страница 6
Простое и эффективное решение указанных проблем представлено на Рис. 6. Код, обрабатывающий срабатывание таймера, упаковывается в отдельный компонент. Когда запускается таймер, этот компонент как аргумент передается таймеру, и когда таймер сработает, через сохраненный аргумент будет вызван код обработки. По такому же принципу можно организовать асинхронный ввод-вывод, обработку прерываний и т. п.
Рис. 6. Уведомление о срабатывании таймера с помощью обратного вызова
Итак, мы рассмотрели типовые задачи, в которых используются обратные вызовы. Как видим, подставляя соответствующие аргументы, можно запускать на выполнение различные участки программного кода. Отсюда можно сделать вывод, что обратные вызовы целесообразно использовать в случаях, когда требуется динамическая модификация поведения программы во время выполнения.
1.3. Модель обратных вызовов
1.3.1. Определения и термины
Модель обратных вызовов изображена на Рис. 7. Структурно она состоит из двух частей: исполнитель и инициатор.
Исполнитель – это компонент, в который упаковывается код обратного вызова (исполняемый код). Исполнитель также содержит контекст, который представляет собой совокупность данных, влияющих на поведение исполняемого кода.
Инициатор – это компонент, который осуществляет обратный вызов. Перед началом работы выполняется настройка, при которой исполнитель как аргумент вместе с контекстом сохраняются в инициаторе. Затем инициатор запускается, и в нужный момент, используя хранимый аргумент, он делает вызов исполняемого кода. В качестве входных параметров в этот код передается сохраненный контекст и информация вызова, которая представляет собой значения, формируемые инициатором.
Рис. 7. Модель обратных вызовов
Дадим формальные определения используемых терминов.
Исполнитель: компонент, который реализует исполняемый код обратного вызова.
Инициатор: компонент, который осуществляет обратный вызов.
Аргумент: хранимая точка входа в код обратного вызова.
Настройка: процедура сохранения аргумента.
Информация вызова: значения, которые формируются инициатором и передаются в исполнитель.
Контекст: множество переменных и состояний, которые влияют на поведение исполняемого кода.
В процессе реализации обратного вызова нам нужно ответить на следующие вопросы.
1. Как оформить исполняемый код, чтобы он мог быть вызван инициатором?
2. Как хранить аргумент?
3. Как передавать контекст?
Различные способы реализации дают свои ответы на поставленные вопросы. Но прежде, чем приступить к их изучению, необходимо осветить еще несколько моментов.