А. Александреску - Современное проектирование на C++ (1119444), страница 65
Текст из файла (страница 65)
Мы определили простой базовый класс вазеИ з1сог, действующий как Глава 1О. Шаблон Н~айог 269 оболочка оператора бупаи1с сазт, и шаблонный класс чтзбтог, генерирующий чисто виртуальную функцию ч1 з)т. Несмотря на то что код, написанный нами для этого, весьма мал, его потенциал повторного использования огромен. Шаблонный класс чба1сог генерирует отдельный тнп для каждого инспектируемого класса. Пользователь библиотеки теперь может не разбивать реализацию на такие маленькие классы, как Рагадгарпч1з1сог и аазсегч1з1тог.
Типы, генерируемые шаблонным классом ч1з1тог, сами обеспечат связь между всеми инспектируемыми классами. Перейдем к инспектируемой иерархии. Как указывалось в предыдущем разделе, инспектируемая иерархия в реализации шаблона дсус11с ч)з1сог предназначена для решения следующих задач. ° Объявлять чисто виртуальную функцию десерт, получающую ссылку на объект класса чтз1тог в базовом классе. ° Замешать и создавать реализацию функции дссерс в каждом производном классе. Для того чтобы выполнить первую задачу, мы добавим в класс вазеч1з(таЫе чисто виртуальную функцию и потребуем, чтобы класс посе1емепс наследовал ее.
Это относится и ко всем корням инспектируемых иерархий, существующих в приложении. Класс вазеч(з1саЫ е выглядит следующим образом. севр1асе <сурепаме я = чо1д> с1азз вазеч1з1таЫе ( риЫ(с: суредет а яесогптуре; ч1гсоа1 -вазеч1з1саЫе0 () ч(гсца1 аесигптуре десерт(вазеч1з1согб) = 0; Все очень просто и скучно. Интересные вещи происходят, лишь когда мы пытаемся выполнить вторую задачу, т.е. реализовать функшао десерт в библиотеке (а не в клиентском коле), Реализация функции десерт невелика, но включать ее в каждый клиентский класс — слишком утомительное занятие. Было бы здорово, если бы этот код принадлежал библиотеке.
Клк предписывает шаблон дсус11с ч1 эт тог, если класс т является инспектируемым, то реализация т:: десерт применяет оператор оупав1 с саас<чз а1 сог<т>*> к типу вазеч1з1сог. Если приведение выполнено успешно, фунай~я т::лссерс возвращает управление функции ч1 з1сог<т>:: ч1 з1с. Однако, как указывалось в разделе 10.2, простое определение функции десерт в классе вазеч1з1саЫе не работает, поскольку параметр *тп1э имеет статический тип вааеч1 з1таЫе, который не может предоставить инспекторам достаточный обьем информации о типе.
Нужно найти способ реализовать функцию десерт в библиотеке, а затем вставить ее в иерархию класса посв1еиепс. Увы, в языке С++ такого непосредственного механизма нет, Есть средства для работы с виртуальным наследованием, однако они работают не лучшим образом. Мы должны прибегнуть к макросу и потребовать, чтобы каждый класс в инспектируемой иерархии использовал этот макрос в своем определении. Принять решение использовать макрос (со всеми вытекающими отсюда последствиями) нелегко, но другого более удобного и эффективного решения не существует. Поскольку программисты на языке С++ люли практичные„эффективность является достаточной причиной для использования именно макроса, а не другого, эзотерического, но неэффективного средства.
270 Часть й, Компоненты Основное правило гласит: макрос должен выполнять как можно меньше работы и как можно быстрее пересылать данные "реальным" сушностям (функции или классу). Макрос для инспектируемых классов определяется следуюшим образом. я6еЯпе ояяхнк чхвхтлвсяО ~ И гсоа1 яесигптуре десерт(вазеИззсогй диезс) ~ ( гесигп лссерсхвр1('сп1з, дмезс); Пользователь должен вставить макрос ояяхнк чхяхтлвьяО в каждый класс инспектируемой иерархии.
Этот макрос переопределяет функцию-член лссерс как шаблонную функцию яссерсхвр1, параметризованную типом параметра *сота. Таким образом, функция Ассерсхвр1 получает доступ к необходимому статическому типу. Функция лссерсхвр1 определена в самом низу иерархии, в классе вазеч1з1сог. Зто позволяет всем производным классам иметь к ней доступ. Вот как выглядит измененный класс вазеч1 зтсаЫ е. сеер1асе <сурепаве я = чо16> с1азз вазеИ з1саЫ е ( рмЫ)с: суре6еу к яесогптуре; чтгсца1 -вазечтз1саЫеО () ч1гсца1 лесцгптуре дссерс(вазечтзтсогш) = О; ргосессе6: // Открывает доступ к иерархии севр1асе <с1аав т> зсасхс яесигптуре лссерсхер1(тб И з1се6, вазеИ азсср доезс) ( // применяем шаблон ясус11с И зтсог тб (ч)ззсог<т>* р = 6упавзс сазс<чтзтсог<т>*>(бдиезс)) ( гесцгп р->И зтс(чтзтсе6); гесигп яесигптуреО; То, что мы отправили функцию яссерсхвр1 в библиотеку, очень важно. Зто сделано не только лля автоматизации работы пользователя.
Присутствие функции лссерсхер1 в библиотеке дает нам возможность настраивать ее реачизацию на конкретные условия проекта. Реализация проекта чзззсог/ч1 з1саЫе скрывает от пользователя большое количество деталей, представляя собой волшебный черный яшик. Ниже приведен код реализации обобшенного шаблона асус11с ч1ззсог. // инспектирующая часть с1авв вазечбз1сог ( риЫ(с: Игсма1 -вазеИз1согО О севр1асе <с1азз т, сурепаве я = чо16> с1авв чтз1сог ( рцЫ1с: суре6еб л яесмгптуре; // доступен для клиентов И гсца1 яесигптуре чззтс(тб) = О; Глава 10. Шаблон Ч~вйог 271 // инспектируемая часть севр1асе <сурепаве а чей> с1авв вааеч(в(саЫе РОЫ тс: суредеУ к кесигптуре; ч( гсиа1 -вакеч(к(саЫеО Ц ч(гсоа1 кесцгптуре Ассерс(вавеч(51согб) О; ргосессед: севр1асе <с1авв т> асас1с аесигптуре АссерСХвр1(тй ч(к(сед, вааеч(к(сог« орекс) ( // применяем шаблон Асус11с ч(втсог 1Г (ч(51сог<т>* р = дупав1с савс<Ч151сог<т>*>Ядоекс)) ( геспгп р->ч151с(ч151сед); гесмгп кесогптуреО; ) ); «дейпе оеяхме чхвхтдвсеО ~ ч(гсиа1 кесогптуре десерт(аааеч(51сог« ооевС) ~ ( гетигп Ассерттвр1(*СП15, Ооект); ) Готовы к тестовым испытаниям? Начнем! с1ааа ОосЕ1евепс: риЫ1с вавеч(в(саЫе<> ( риЫ1с: 0екхме чх5хтАВсеО с1ава Рагаягарп : роЫ 1с оосе1евепс риЫ(с: оекхме чхвхтАВсеО ); с1аве МуСопсгетеч(з(Сог : риЫ(с вавечтв(сог, // необходим риЫ(с Ч151сог<оосе1евепс>, // инспектирует объекты // класса оосе1евепсв риЫ(с ч(в(сог<Рагадгари> // инспектирует объекты // класса Рагадгарь ( роЫ(с: чо(о ч151с(оосе1евепсй) ( 5М::собес « "Ч151с(оосе1евепсб) сп"; ) чо10 Ч151с(Рага9гарпе) ( ксб::соос « "чхвтс(Рага9гарМ) ~п"„' 1пс ва(пО Мусопсгетеч(51'Сог ч(з(Сог; Рагацгарн раг; оосе1евепс* д = браг; // скрывает статический тип объекта раг д->Ассерс(ч151сог); Часть И.
Компоненты Зта маленькая программа выводит на экран сообщение, означающее "все в порялке'*. ч151с(рагацгарпй) Разумеется, этот искусственный пример не может продемонстрировать всю мощь разработанного нами кода. Однако, если вспомнить, с какими трудностями мы столк- нулись в предыдущем разделе при реализации инспекторов "с нуля", становится ясно, что теперь в нашем распоряжении есть средство для корректного создания инспекти- руемых иерархий и их дальнейшего инспектирования.
Перечислим действия, которые необходимо выполнить при определении инспек- тируемой иерархии. ° Вывести корень иерархии из класса вазеч1ззсаЫе<чоогяеспгптуре>. ° Добавить в каждый класс 5оаеС1азз, входящий в инспектируемую иерархию, макрос 0ЕЕ1НЕ Ч151ТАВЬЕО. (Теперь иерархию можно инспектировать, однако никаких зависимостей от класса ч! з)сог больше нет!) ° Вывести каждый конкретный инспектирующий класс из класса вазеч1з1сог. Кроме того, для каждого класса х, подлежащего инспектированию, нужно вывести класс 5овеч151сог из класса ч1з1сог<х, чопгяеспгптуре>.
Обеспечить замещение функции-члена чз в1с в каждом инспектируемом классе. Диаграмма зависимостей, возникающая в результате этих действий, очень проста. Определение класса 5овеч1в1сог зависит по имени от каждо~о инспектируемого класса. Реализации функции-члена чзэ)с полностью зависят от классов, которыми они манипулируют. Все это прекрасно. По сравнению с реализациями, обсуждавшимися ранее, луч- шего и быль не может.
Благодаря реализации шаблона ч1з1сог у нас есть упорядо- ченный способ создания инспектируемых иерархий, позволяющий сократить клиент- ский код и зависимость классов. В особых случаях функцию Ассерс лучше реализовывать непосредственно, а не с по- мощью макроса оее1не ч151тдвьеО. Допустим, что мы определяем класс 5есс1оп, про- изводный от класса оосе1 евепс и содержащий несколько объектов класса вагап гаро. Мы бы хотели инспектировать все объекты класса еагацгарп, содержащиеся в объекте класса 5есс(оп.
В этом случае мы могли бы реализовать функцию Ассерс вручную. с!ада 5есс(оп : рио1(с оосЕ1евепс // ВуихцИя АссерС реализуется непосредственно, // а ие через макрос оеетне ч151тдвьеО чз гсва! яесогптуре Ассерс(вааеч(з1согб ч) ( тог (каждый параграф в данном разделе) сиггепс рагадгарй->Ассерс(ч) ) Очевидно, что с помощью шаблона ч151сог можно делать все, что угодно. Код, приведенный выше, освобождает программиста от тяжелой необходимости создавать все с нуля. Мы закончили разработку ядра реализапии шаблона ч(в(сог, содержащего практически все, что необходимо для инспектирования.
Продолжение следует. 273 Глава 10. Шаблон Ч1вйог 10.5. Назад — к "простому" шаблону Ч!аког Реализация обобщенного шаблона деус)1с чза1хог, определенная в предыдущем разделе, вполне удовлетворительно работает во многих ситуациях. Однако, если нужно создать быстродействующее приложение, динамическое приведение типов, выполняемое в функции десерт, может повергнуть вас в уныние, а измерения скорости работы вашей программы — прямо в состояние депрессии.