А. Александреску - Современное проектирование на C++ (1119444), страница 69
Текст из файла (страница 69)
Приведенный выше код работает лишь с тремя классами, но уже имеет значительный размер. С увеличением количества классов размер кода возрастает по экспоненциальному закону. Представьте себе эту программу, если иерархия состоит из 20 классов! Вторая проблема заключается в том, что реализация функции ооиЫеодзрассй должна иметь информацию о существовании всех классов, входящих в иерархию. Третий недостаток функции ооцЫео)зрассп заключается в том, что порядок следования операторов 1У имеет большое значение. Это очень сложная и опасная проблема.
Вообразите, например, что из класса яессапд1е выводится класс яоипдедяессапд1е (прямоугольник с закругленными углами). Мы редактируем функцию ооиЫе01зрассп, вставляя дополнительные операторы 1б в конец каждого оператора 1зе1зе, непосредственно перед вызовом функции Еггог.
Вот и ошибка! Если функции ооцЫео1зрассп передается указатель на класс яоцпдедяессапд1е, оператор дупае1с сазс<яессапд1е*> выполняется успешно, Поскольку эта проверка выполняется до проверки дуоае1с сазс<яоцпдедяессапц1е+>, первая проверка "проглотит" и класс яессапд1е, и класс яоцпдедяессапц1е. Вторая проверка становится излишней, Большинство компиляторов это совершенно не волнует. Эти проверки следует немного изменить.
чо1д ооиЫео1зрассп(>парей 1пз, >парей гпз) 1б (суре1д(1пз) = суре1д(аессаоц1е)) ( яессапд1е* рХ ~ дупая1с сазс<яессапц1е*>(Ыпз); е1зе . Теперь проверка выполняется только для точного типа, а не для точного или производного. Сравнение суредд выдаст отрицательный результат, если указатель 1пз ссылается на объект класса яоипдедяессапд1е, поэтому проверки будут продолжены. В итоге проверка суресд(яоипдедяессапц1е) будет выполнена успешно.
Увы, исправляя один недостаток, мы создаем другой: функция ооиЫео1зрассп становится слишком жесткой. Если в ней не предусмотрен какой-нибудь тип, естественно ожидать, что функция ооиЫео1зрассп сработает на ближайшем к нему базовом типе. Именно на это мы обычно рассчитываем — по умолчанию производные объекты представляют собой базовые объекты, за исключением некоторых замешенных свойств. Проблема заключается в том, что реализация функции оооЫео1зрассп на основе оператора суре16 этой возможностью не обладает, Отсюда следует, что в функции поиЫео1зрассп нужно по- прежнему использовать оператор дупалп с саь с и упорядочить проверки 1т, чтобы наиболее далекие потомки базового класса обрабатывались первыми.
Глава 11. Мультиметоды Так возникают еще два недостатка грубого подхода к реализациям мультиметодов. Во-первых, зависимость функции поио1еп(зрассп от иерархии класса Епаре увеличивается -- функция должна знать не только о существовании классов, но и об их взаимоотношениях. Во-вторых, поддержка соответствуюпзей систематизации динамических приведений типов связана с дополнительной нагрузкой. 11.4. Автоматизированный грубый подход Поскольку в некоторых ситуациях быстродействие грубого подхода оказывается непревзойденным, имеет смысл реализовать такой диспетчер.
Напомним, что в библиотеке (.ох) определены списки типов — наборы структур и статических алгоритмов, позволяющих манипулировать коллекциями типов. Грубая реализация мультиметодов может использовать предоставленный пользователем список типов, в котором перечислены классы, входящие в иерархию (в нашем примере— классы аессапо1е, Ро1у, Е11(рзе и др.).
Затем рекурсивный шаблон может генерировать последовательность операторов 11-е1зе. В общем случае мы можем работать с разными коллекциями типов, поэтому список типов для левого операнда может отличаться от списка типов для правого операнда. попробуем набросать шаблонный класс атас(сп(зрасснег, выполняющий алгоритм вывода типов и вызывающий функцию из другого класса.
сеер1асе с1азз ехесцсог, с1азз вазеьйз, с1ааа туреьпз, с1азз вазеапз = вазеспз, с1ава туревапз = турезьйз сурепаее аезц1стуре = чо1д с1ааа есас1сп1зрасспег ( суредет' сурепаее турезьйз::неад неад; суредеУ сурепаее турезьйз::таз1 та11; рио1(с: асас1с яезц1стуре ао(вазеьпзй, вазеапзб, ехесисог ехес) ( (У (неад* р1 = дупае1с сазс<неад*>(Ипз)) ( гесцгп есас(сп1зрасспег<ехесцсог, вазеепз, нц11туре, вазеапз, турезапз>::рззрасспапз( *р1, гпз, ехес); е1зе ( гесцгп Есасчсп(зрасспег<ехесисог, вазеейз, та(1, ВаЗЕаПЗ, турааапа>:Пве( 1йз, гпз, ехес); 286 Часть й. Компоненты Класс 5Сатз соз зратспег устроен довольно просто.
Учитывая сложность задачи, которую он решает, размер его кода удивительно мал. Класс 5сас1сп1зрасспег имеет шесть шаблонных параметров. Класс дхесцсог — эти тип объекта, выполняющего реальную работу, — в нашем примере он закрашивает область пересечения фигур. Его внутреннее устройство мы рассмотрим немного позднее. Классы вазеьпз и вазеапз представляют собой базовые типы аргументов левого и правого операндов соответственно.
Классы туреьпэ и туреапз — это списки типов, состоящие из возможных производных классов для этих двух аргументов. По умолчанию классы вазекоз и турезапз реализуют диспетчер лля типов, входящих в одну и ту же иерархию, как в программе для рисования. Класс яеац1стуре — это тип результата двойной диспетчеризации. В общем случае вызываемая функция может возвращать произвольный тип. Класс 5СаС1сп1зрасспег поддерживает такую степень обобщенности и возвращает результат вызывающему модулю. Функция 5СаС)сот зрасспег::бо выполняет динамическое приведение адреса 1пз к первому типу, указанному в списке турезьпэ. Если динамическое приведение оказалось невыполнимым, функция По передает хвост списка турезьпз своему рекурсивному вызову.
(На самом деле это не настоящий рекурсивный вызов, поскольку каждый раз производится новая конкретизация класса 5сас1сп1зрасспег.) В итоге функция по выполняет подходящий оператор 1б-е1зе, применяющий оператор дупав1 с сазс к каждому типу, указанному в списке. Обнаружив соответствие, функция ао вызывает функцию п1зрасспапз. Это второй и последний этап вывода типа — обнаружение динамического типа объекта гпз. севр1асе <...> с!азэ 5сас1спдзрасспег ( .. ~ ках и раньше Севр1аСе «с1аэз 5овеъпэ> згас1с аеьц1стуре п1зрасспапз(5овеьпзй 1йз, вазекпзб гпз, ЕХесЦСОГ ехеС) ( суредеГ сурепаве турезапз::неад неад; суреде1 сурепаве турезапз::та11 та11; 11 (неад* р2 = дупав1с сазс«неад*>Ягпз)) гесигп ехес.
г(ге(1пз, *р2); е1зе ( гесцгп 5сас1сп1зрасспег<дхесцсог, 5овеьпз, нц11туре, вазекпз, та11>::п1зрасспкпэ( 1пз, гпз, ехес); ); Функция п1 зрасспапз выполняет лля объекта гоэ тот же алгоритм, который применялся функцией по для объекта 1 па. Кроме того, если динамическое приведение типа гпэ выполнено успешно, функция п1зрасспяпз вызывает функцию дхесосог: г л1ге, передавая ему два определенных типа. Как и прежде, код, генерируемый компилятором, представляет собой набор операторов за-е1зе. Интересно, что компилятор генерирует для казкдого типа, указанного в списке турезьпз, отдельный набор операторов 15-е1зе. Действительно, класс 5сас1сп1зрасспег порождает экспоненпиальный обьем кола, имея лва 287 Глава 11.
Мультиметоды списка типов и фиксированный базовый код. Зто ценное, однако потенциально опасное качество — слишком большой код может потребовать слишком много времени иа этапе компиляции, негативно повлиять на размер программы и обшее время ее выполнения. Для того чтобы установить ограничения на статическую рекурсию, нам нужно специализировать класс 5татз спз зратсПег для двух ситуаций, когда тип объекта 1П5 в списке турезьП5 и тип объекта гйа в списке турезяйв не найдены.
Первая ситуация (неверный объект 1П5) возникает при вызове функции по из класса 5татзспззратсПег, когда в качестве параметра турезьбзт ему передается класс ни11туре. Зто признак того, что во время поиска список турезьП5 оказался исчерпанным. (Напомним, что класс ни11туре используется в качестве признака конца списка типов.) теир1ате < с1азз Ехесцтог, с1а55 Ва5еьП5, с1ааз вазеяП5, с1а55 турезяП5, турепаие яеав1ттуре > с1а55 5тат1спбзратСПег<ехеситог, вазЕЬП5, Нц!1туре, вазеяП5, турезяП5, вези1ттуре> зтатбс чозб ао(вазеьПзй 1П5, вазевП56 гП5, ехесцтог ехес) ( ехес.опеггог(1П5, гП5); Обработка ошибок элегантно делегируется классу ехеситог, который будет рассмотрен ниже. Вторая ситуация (неверный объект гйа) возникает при вызове функции пззратсПяП5 из класса 5тат1сп15ратсПег, когда в качестве параметра турезяйа ему передается класс нц11туре.
тевр1ате с1ааа ехесцтог, с1ааа ВазеЬП5, с1а55 турезьП5, с1аза вазеяП5, с1азз турезкП5, турепаве яези1ттуре > с1авз 5гат)срззРатсПег<ехеситог, вазЕЬП5, турезЬП5, ВазЕЯП5, Нц11туре, яези1ттуре> риП1)с: зтатзс ссссо ПззратсПяП5(аазЕЬПзб 1Пз, ВазЕКП58 гП5, ехеснтогб ехес) ехес.опеггог(1П5, гП5); Теперь настало время обсудить, как нужно реализовать класс ехеситог, чтобы использовать преимушество механизма двойной диспетчеризации, определенного выше.
Класс 5тат)спззРатсПег лишь распознает типьь Обнаружив правильные типы и обьекты, он передает их функции ехесцтогс се(ге. Чтобы различать вызовы этой Часть с( Компоненты 288 функции, класс ехесисог должен реализовать несколько перегрузок функции ел ге. Например, класс ехесисог для закрашивания области пересечения двух фигур выглядит следующим образом.