А. Александреску - Современное проектирование на C++ (1119444), страница 30
Текст из файла (страница 30)
Класс гопстог, облалаюший возможностью вызывать любую функцию-член любого объекта, мог бы оказать в этом деле неоценимую помощь. В классе гипстог стоит реализовать и некоторые из активных команд, например, упорядочение нескольких последовательных операций. Тогда объект класса гистог мог бы собирать несколько операций и выполнять их по порядку. В книге Оапиша ег а1. (1995) такие полезные объекты описаны под именем масгоСоахаапд.
5.2. Шаблон Сопппапб а реальном мире Применение шаблона савваМ часто иллюстрируют на примере разработки оконного интерфейса. В хороших объектно-ориентнрованных специализированных системах лля разработки графического пользовательского интерфейса шаблон Соаааапд в той илн иной форме применяется уже многие годы. Соътазелям оконных интерфейсов необходим стандартный способ передачи пользовательских дейсзвий (например, щелчков мыши нли нажатий клавиш) приложению. Когда пользователь щелкает на кнопке, выбирает пункт меню или делает что-нибудь подобное, оконная система должна уведомить об этом соответствующее приложение.
С точки зрения оконной системы команда орт(опз в меню тоо1з не имеет никакого особого смысла. Это накладывает на приложение очень жесткие ограничения. Объект класса Соаэаапд позволяет эффективно изолировать приложение от окониого интерфейса, выступая в роли посредника, сообщающего приложению о действиях пользователя. 1га Часть й.
Компоненты 5.3. Вызываемые сущности в языке С++ Для того чтобы создать обобщенную реализацию команд пересылки, попытаемся описать связанные с ними понятия в терминах языка С++. Команда пересылки представляет собой обобщенный обратный вызов (Вепегайхед саИЬас)г). Обратный вызов — это указатель на функцию, который можно передавать и вызывать в любое время, как показано в следующем примере. чо1г) еооО; тотг) ВагО; тпт юа1пО ( // Определяем указатель на функцию,не имеющую // параметров и возвращающую значение типа чозд. // инициализируем этот указатель адресом функции еоо чозд (*ре) О = Йеоо; гооО; // непосредственный вызов функции еоо; вагО; // непосредственный вызов функции ваг; (*Вг) О; // вызов функции еоо через указатель рг чо)6 (*ре2) О = рг; // создаем копию указателя ре рг = оваг; // устанавливаем указатель рг // на функцию ваг // вызов функции ваг через указатель рг // Вызов функции Еоо через указатель рГ2 (*ре) О ~ (*ре2)О Межлу непосрелственным вызовом Функции гоо и вызовом через указатель (*рг)' есть олно существенное отличие.
Во втором случае указатель можно копировать, глето хранить и устанавливать на другую функцию, вызывая ее при необходимости. Следовательно, указатель на функцию очень похож на команду пересылки, в которой значительная часть работы хранится отдельно от объекта, выполняющего фактическую обработку данных. ' Компилятор предлагает синтаксическое сокращение: вырюкение (*рЕ) О эквивалентно реО. Однако выражение (=ре) О более наглядно отрюкает суть происходящего — указатель рЕ разыменовывается н к нему применяется оператор вызова функции О.
125 Глава б. Обобщенные функторы Например, в оконной системе в роли вызывающих объектов выступают элементы интерфейса (кнопки, пункты меню и т.п.), а получателем является объект, определяющий реакцию приложения на действия пользователя (например, лиалоговое окно или само приложение).
Объекты класса соющапд представляют собой средство общения между пользовательским интерфейсом и приложением. Как указывалось в предыдущем разделе, класс Соююапд обеспечивает удвоенную гибкость. Во-первых, в оконную систему можно встраивать новые элементы пользовательского интерфейса, не изменяя логику работы приложения. В таких случаях говорят, что программа имеет оболочку (зЫппаЫе), поскольку новые элементы интерфейса можно добавлять, не касаясь салюго приложения. Оболочки не имеют никакой архитектуры — они только предоставляют места лля объектов класса соююап0 и знают, как с ними взаимодействовать, Во-вторых, одни и те же элементы пользовательского интерфейса можно повторно использовать в разных приложениях. Действительно, обратный вызов — зто способ использования шаблона совюапд в языке С.
Например, система Х %!пдомз хранит такой обратный вызов для каждого пункта меню и каждого элемента интерфейса (мчдаег). Когда пользователь выполняет определенные действия (например, щелкает на кнопке), соответствующий компонент интерфейса активизирует обратный вызов, причем компонент не знает, что при этом происходит. Кроме простых обратных вызовов, в языке С++ есть еше много сущностей, поддерживающих операторо.
° Функции. ° Указатели на Функции. ° Ссылки на функции (по существу, прелставляюшие собой константные указатели на Функции). ° Функторы, т.е. объекты, в которых определен оператор О. ° Результат применения операторов .* и ->* к указателям на функции-члены. К каждой из указанных сущностей можно приписать пару круглых скобок, задать внутри них набор аргументов и выполнить какую-нибудь обработку данных. С другими сущностями в языке С++, кроме перечисленных выше, этого делать нельзя. Сущности, позволяющие применять оператор О, называются вызываемыми (са!1аЫе).
Цель этой главы — реализовать набор команд пересылки, способных хранить и передавать вызов любой вызываемой сущности'. Шаблонный класс ьцпстог инкапсулирует команды пересылки и обеспечивает единообразный интерфейс. Реализация должна учитывать три основных варианта: простые вызовы функций, вызовы функторов (включая вызовы объектов класса гцпстог, т.е, возможность передавать вызовы от одного объекта класса ъцпстог другому) и вызовы функций-членов. Для этого следует создать абстрактный базовый класс и подкласс для каждого класса.
На первый взгляд все это можно сделать с помощью средств языка Сч-ч-. Однако, как только вы приступите к разработке программы, на вас обрушится ворох проблем. 5.4. Скелет шаблонного класса Рнпс1ог Для класса гцпстог обязательно следует проверить идиому "дескриптор-тело" ("Ьапб!е-Ьоду"), впервые предложенную в работе (Сор!!еп, 1992). В главе 7 подробно объясняется, что в языке С++ голый указатель на полиморфный тип не имеет полноценной семантики из-за проблем, связанных с его принадлежностью. Для того чтобы снять ответственность за управление временем жизни объектов с клиентов класса гцпстог, лучше всего наделить класс ъцпстог семантикой значений (хорошо определенными операциями копирования и присваивания).
Класс гцпстог имеет полиморфную реализацию, но этот факт скрыт внутри него. Назовем реализацию базового класса именем гопстогтмр). Отметим важную особенность: функция Совпасл::бхЕСЦте Шаблона Союпапг! в языке С++ перевоплощается в оператор О, определенный пользователем. Это еше один аргумент в пользу применения оператора О. Для программистов на языке С++ з Обратите внимание иа то, что мы ничего не говорим о типах. Мы могли бы просто сказать; "Типы, допускаюшие выполнение оператора (), называются вызываемыми сушностями".
Однако, хотя зто кажется невероятным, в языке С++ есть сущности, не имеющие типа и в то же время позволяющие применять оператор О. 126 Часть! !. Компоненты оператор вызова функции имеет точный смысл — "выполнить". Однако намного важнее то, что этот оператор обеспечивает синтаксическое единообразие. Класс гипссог не ~олько передает вызов вызываемой сущности, он и сам является таковой. В подобном случае объект класса гопссог может содержать другие объекты этого класса. С этого момента мы будем считать класс гипссог частью множества вызываемых сущностей. Это позволит нам многие вещи делать единообразно. Проблемы возникнут, как только мы попытаемся определить оболочку класса гипссог.
Вначале она может выглядеть следующим образом. с1азз гцпссог ( рцЬ1(с: чо)д орегасогО О; // другие функции-члены ргбчасе: // реализация класса Первый вопрос: какой тип должно иметь значение, возвращаемое оператором О? Должно ли оно иметь тип чолб? В некоторых случаях может понадобиться вернуть что-то еше, например, значение типа Ьоо! или зсб::зсгзпй. Нет никаких причин отказываться от возврата параметризованных значений.
Для решения таких проблем предназначены шаблонные классы, позволяющие избежать лишних хлопот. севр1асе <сурепаае везц1стуре> с1азз гцпссог ( рцЬ)тс: яезц!стуре орегасогО О; // другие функции-члены рг(часе: // Реализация Это решение выглядит вполне приемлемо, хотя теперь у нас уже не один класс гцпссог, а целое семейство. Это вполне разумно, поскольку функторы, возвращающие строки, и функторы, возвращающие целые числа, имеют разные функциональные возможности.
Второй вопрос: должен ли оператор О, принадлежащий функтору, также получать аргументы? Может возникнуть необходимость передать объекту класса гцпссог некую информацию, которая была недоступна в момент его создания. Например, если щелчки мышью в окне пересылаются через функтор, вызывающий объект передает информацию о координатах окна (известную только в момент вызова) объекту класса гипссог, вызывая его оператор О, Более того, в обобщенном программировании количество параметров не ограничивается, причем они могут иметь любой тип. Таким образом, нет никаких причин накладывать ограничения ни на количество, ни на тип передаваемых функтору параметров. Отсюда следует, что каждый функтор определяется гипом возвращаемого им значения и типами его аргументов. Для этого понадобится мощная языковая поллержка: переменные шаблонные параметры в сочетании с переменными параметрами вызова функций.
К сожалению, переменных шаблонных параметров в языке С++ просто нет, Вместо ннх предусмотрены функции с переменными аргументами (так же, как и в языке С). В языке С с их помощью выполняется значительная часть работы (при условии, что вы 127 Глава 5. Обобщенные функторы очень осторожны), но лля языка С++ этого недостаточно. Переменные аргументы поддерживаются с помощью эллипсисов (таких как рг(птт или зсапт). Вызов функций рг(птФ или асалам, когда спецификация формата не соответствует количеству и типу их аргументов, — весьма распространенная и опасная ошибка, иллюстрирующая недостатки эллипсисов. Механизм переменных параметров ненадежен, относится к низкому уровню и не соответствует объектной модели языка С++.
Короче говоря, используя зллипсисы, вы остаетесь без типовой безопасности, объектной семантики (применение объектов, содержащих эллипсисы, приводит к непредсказуемым последствиям) и ссылочных типов. Для вызываемых Функций недоступно даже количество их аргументов. Действительно, там, где есть эллипсисы, языку С++ делать нечего. В качестве альтернативы можно ограничить количество аргументов функтора произвольным достаточно большим числом.
Такая неопределенность — одна из самых неприятных проблем для программиста. Однако этот выбор можно сделать на основе экспериментальных наблюдений. В библиотеках (особенно старых) количество параметров функций не превышает 12. Установим предельное количество параметров равным 15 и забудем об этой неприятности. Даже это не облегчило нашей участи. В языке С++ не допускается применение шаблонов, имеющих одинаковые имена, но разное количество параметров.