Обратные вызовы в C++ | страница 5
Рис. 4. Результат вычисления с помощью обратного вызова
1.2.3. Перебор элементов
Представим, что мы разрабатываем модуль сетевого обмена. Как пользователю узнать, какие протоколы поддерживаются?
Самое простое решение – получить количество поддерживаемых протоколов, а затем запрашивать их имена по порядковому номеру. Данный способ легко реализуем, если внутри модуля имена протоколов хранятся в массиве. А если имена нужно хранить в списке? Тогда задача усложняется: нужно сделать перебор элементов списка, чтобы получить нужное значение по порядковому номеру. А если имена должны храниться в виде двоичного дерева?
Возможное решение: разработать итератор – специальный класс, который будет осуществлять навигацию по контейнеру. Такой подход реализован, к примеру, в стандартной библиотеке STL, где для каждого контейнера имеется соответствующий итератор. Недостаток этого решения проявляется в том, что мы ограничиваем сферу применения модуля, построенного таким образом: его использовать могут только те компоненты, которые способны интерпретировать вызовы методов C++. Кроме того, итератор привязан к типу используемого контейнера, и при его изменении приходится перекомпилировать все связанные компоненты.
А что, если реализовать итератор с помощью набора функций, без использования классов? Интерфейс получается довольно сложным: необходимы отдельные функции для создания итератора, запроса значений, уничтожения итератора; необходимо объявить тип данных для хранения итератора; необходимо предусмотреть уничтожение итератора в случае возникновения исключений.
Простое и эффективное решение указанных проблем представлено на Рис. 5. Код, обрабатывающий имена поддерживаемых протоколов (например, отображение в пользовательском интерфейсе), упаковывается в отдельный компонент. Для получения протоколов вызывается функция, в которую указанный компонент передается как аргумент. Функция перебирает хранимые значения, для каждого значения через сохраненный аргумент вызывается код обработки, имя протокола передается как параметр.
Рис. 5. Просмотр элементов с помощью обратных вызовов
1.2.4. Уведомление о событиях
Представим, что мы в системе запустили таймер, и нам нужно получить уведомление о срабатывании таймера. Самое простое решение – в процессе выполнения опрашивать таймер и анализировать, не истекло ли время. Как часто нужно делать опрос? Слишком часто – теряется производительность, слишком редко – теряется точность. Кроме того, приходится постоянно в определенных участках кода вставлять вызов опроса. Учитывая, что в программе могут работать несколько потоков, опрашивать таймер они будут с разной частотой, и каждый поток обнаружит срабатывание таймера в разное время.