А. Александреску - Современное проектирование на C++ (1119444), страница 70
Текст из файла (страница 70)
с1азз НаСсЫпдЕхесиеог риб1зс: О Разные алгоритмы закрашивания области пересечения чотд езге(яессапд1ей, яессапд1ей); чот'д Езге(яессапд1ей, Е115рзей); чо1д ес ге(яессапд1ей, яо1уй); чеза е)ге(е111рзей, яо1уй); чита ет ге(е111рзей, е111рзей); чо1б езге(яо1уй, ео!уй); О еункция для обработки ошибок чот'д Опеггог(5барей, 5барей); ); Класс нассЫпдехесисог используется вместе с классом 5сасзсотзрассбег. суредеУ 5Сатзспззратсбег<нассЫпдехесисог, 5баре, тчрепзт 3(яессапд1е, е115рзе, яо1у)> обзрассЬег; 5баре *р1 5баре *р2 = ...; нассЫпдехесисог ехес; оззрассбег:нзо(*р1, *рЕ. ехес); Этот код вызывает соответствующую пере1рузку функции езге из класса нассЫпдехесисог. Шаблонный класс 5сас1собзрассбег можно рассматривать как механизм динамической перегрузки — он откладывает перегрузку на период выполнения программы.
Это значительно облегчает использование класса 5сасзсоззрассбег. Нужно лишь реализовать класс нассЫпдехесисог, имея в виду правила перегрузки, а затем использовать класс 5сасбсоз зрассбег в качестве черного ящика, применяющего правила перегрузки во время выполнения программы. Класс 5сасз соззрассбег имеет побочный эффект — он распознает и перетружает неоднозначности на этапе компиляции. Допустим, что мы вместо функции нассбт пдехесисог:: е1гесе111 рзей, ро1 уй) объявили функцию нассы пдехесисог:: ез ге(5барей, яо1 уй). Вызов функции нассЫ пдехесисог:: ет ге с параметрами типа Е111рзе и ро1у может породить неоднозначность — обе функции соответствуют одному и тому же вызову.
Интересно, что класс 5Сатзспчзрасспег также обнаруживает эту ошибку и выдает о ней такое же детальное сообщение. Класс 5сасчсотзрасспег полностью соответствует правилам перегрузки, существующим в языке С++. Что случится, если во время выполнения программы обнаружится ошибка — например, если в качестве одного из аргументов функции 5саС)сц1зрассбег::ао передать класс СЕ гс1е". Как уже упоминалось, в таких ситуациях класс 5Сатз сот зратсбег просто вызывает функцию ехесисог::Опеггог, параметрами которой являются исходные (не преобразованные) объекты 1бз и гбз. Это значит, что в нашем примере функция нассЫпдехесисог::Опеггог(5барей, 5барей) представляет собой модуль обработки ошибок. Эту Функцию можно применять по своему усмотрению — ее вызов означает, что класс 5сас1сотзрассйег приступает к поиску динамических типов. Как указывалось в предыдущем разделе, при ~рубой реализации диспетчера наследование порождает новые проблемы.
Следовательно, приведенная ниже конкретизация класса 5сасзсптзрассбег содержит ошибку. Глава 11. Мультимвтоды 289 суредес 5СаС)со)зрассоег < 5оведхесотог, 5ларе, тчяеьс5т 4(яессапц1е„ е11)рзе, го1у, яоцпдедяессапц1е) > муо)зрассбег; Если шаблонному классу муо)зрасспег передается класс яоцпдедяессапц1е, он будет рассматриваться как класс яессапц)е. Оператор дупав)с сазс<яессапц1е*> успеш- но применяется к указателю на класс аоцпдедяессапц1е, а поскольку оператор дупав)с савс<яоцпдедяессапц1е*> находится в цепочке проверок ниже, он никогда не будет выполнен. Правильная конкретизация выглядит следующим образом. суредег 5сас)со)зрассоег < 5овеЕхесцСОг, 5паре, тчяесс5т 4(яоипдедяессапц1е, е11)рзе, яо1у, яессапц1е) > о)зрассоег; Общее правило таково: наиболее отдаленные потомки базового типа должны находиться в начале списка типов.
Было бы прекрасно, если бы это преобразование выполнялось автоматически. Списки типов позволяют это сделать. У нас есть средство для обнаружения наследования во время компиляции (глава 2), причем списки типов можно упорядочить. Таким образом, можно воспользоваться статическим алгоритмом оегзчедтоягопс, описанным в главе 3. Для автоматической сортировки списка нужно лишь модифицировать реализацию класса 5СаСт спт зратспег следующим образом. севр1асе <...> с1аав 5сасзсоззрасспег ( суредет сурепаве оег)чедтоегопу< сурепаве туреьпз::неад>::яезц1с неад; суредег сурепаве оегзчедтоегоос< сурепаве турезспз::та11>::яезц1с таз1; рцб1)с: как и раньше ...
); Обеспечив удобную автоматизацию, нс забудьте, что мы получаем в результате программу, генерирующую код. Проблема, связанная с существованием зависимостей между классами, остается нерешенной. Несмотря на то что грубую реализацию мультиметодов очень легко выполнить, класс 5Сатссотзрасспег по-прежнему зависит от всех типов, входящих в иерархию. Его преимушества — это быстродействие (если количество классов в иерархии не слишком велико) и ненавязчивость (поп|пццз|чепезз), которая выражается в том„что для использования класса 5сасз сот зрасс)зег нс нужно модифицировать иерархию типов. 11.5. Симметричность грубого подхода Штриховка пересечения двух фигур может зависеть от того, как именно пересекаются фигуры: прямоугольник накрывает эллипс или наоборот. Иногда это не имеет 290 Часть й. Компоненты значения, и штриховка в обоих случаях должна оставаться одинаковой.
В этом случае нужен симметричный мульл1ииелюд (зупипе!пс пш!!!те!пой), который не зависит от порядка следования его аргументов. Симметрия применяется, только если типы обоих параметров илентичны (в нашем случае класс вазеспэ совпадает с классом Ваэеапз, а спвтурез совпадает с классом апзтурез). Класс всас1со1зрасспег, реализованный в рамках грубого подхода, является асимметричным. Это значит, что он не полдержнвает симметричные мультиметоды.
Допустим, например, что мы определяем следующие классы. с1азз нассЫпдехесцСог рц!з11с: чо1д е(ге(яессапд1ей, яессапд1ей) чо10 Е1ге(яессапд1ей, е111рзей) // обработчик ошибок чо10 опеггог(епарей, тварей); суредеУ ЕСат)со1зрассиег < НатсЫпдехесцтог, Епаре, ттрессет 3(яессапд1е, е111рзе, во1у) > нассЫпдо1зрасспег; Класс нассЫ пдо1 зрасспег не срабатывает, если ему передать в качестве левого параметра класс е111рзе, а в качестве правого параметра — класс аессапд1е. Даже несмотря на то, что с точки зрения класса нассЫпдехесисог не имеет никакого значения, какой из этих параметров является левым, а какой — правым, класс нассп1пдо1зрасс!зег настаивает, чтобы объекты передавались в определенном порядке. Поменяв аргументы местами и применив другую перегрузку в клиентском коде, можно исправить этот недостаток, с1ава нассЫпдехесцсог ( ри!з11с: чоИ е1ге(аессапд1ей, е111рзей); // обеспечиваем симметрию чо(д Е1 ге(Е111 раей 1пз, кессапд1ей гпз) ( // переходим к функции Ебге(яессапд1ей, Е111рзей), // меняя порядок аргументов Е1ге(гпз, 1пз); Зта небольшая функция обеспечивает симметричность мультиметода.
В идеале класс Ьсас1со1зрасспег должен был бы сам предоставлять эту возможность с помощью дополнительного булевского шаблонного параметра. Для этого нужно, чтобы в некоторых ситуациях класс атас)со1зрассЬег менял порядок следования аргументов при обратном вызове.
В каких именно ситуациях? Проанализируем предыдущий пример. Дополняя список шаблонных аргументов их значениями, заданными по умолчанию, мы получаем следующую конкретизацию. 291 Глава 11. Мультиметоды туредеУ 5тат(сп1зратспег < натсЫпдехесытог, 5иаре, тчреетвт 2(аестапд1е, е111рзе, Ро1у), // класс турезепз 5паре, тчяеьх5т 2(аестапд!е, е11(рзе, Ро1у), // класс турезапз чоз д > натсп1пдп(зрастНег; В симметричном диспетчере можно применить следующий алгоритм выбора пар параметров: обьедииим первый тип из первого списка типов (класс туреьйз) с кажлым типом из второго списка (класс турезяпз). Эго порождает три комбинации: яестапд1еяестапд1е, аестапд1е-е11(рзе и яестапд1е-Ро1у.
Затем объединим второй тип из списка турез 'пз с типами из списка турезапз. Однако, поскольку первая комбинация (яестапд1е-е11(рзе) уже была создана на первом этапе, на этот раз алгоритм начинается со второго элемента в списке турезайз. Теперь порождаются пары е11зрзе-е11зрзе и Е)1(рве-Ро1у. Тс же рассуждения применяются на следующем шаге: объединение кчасса Ро1у из списка турезьпз с типами из списка турезапз начинается с третьего элемента. На этом этапе порождается только одна комбинация — Ро1у-ро1у. Следуя этому алгоритму, мы реализуем функции только для полученных комбинаций. с1азз натсЫпдехесытог ( рыЫзс: чу Р1ге(яестапд1еш, кестапд1ей); чотб Рз ге(аестапд1еш, Е11(рзеш); чотг( Рл ге(аестапд1еш, Ро1уб); чозг( Рз ге(Е11(рзеб, Е111рзеб); чо(г( Р(ге(Е11з раей, Ро1уш); чо(з Р(ге(Ро1уш, Ро1уш); // Обработчик ошибок чеза Опвггог(5пареб, 5парей); ); Класс 5тат1со(зратспег должен распознавать комбинации, исключенные нами из описанного выше алгоритма, а именно: е11(рзе-аестапд1е, Ро1у-яестапд1е и Ро1уе11(рзе.
Для этих комбинаций класс 5тат(спззратспег должен менять аргументы местами, а в остальных случаях поступать, как и прежле. Какое булевское условие позволяет определять, когда следует менять аргументы местами, а когда нету Описанный выше алгоритм выбирает в списке ть2 лишь типы, имеющие индексы, равные или лревышающие индекс типа в списке ть1. Следовательно, условие формулируется следующим образом. Если индекс типа ц в списке турезяйз меньше, чем индекс типа т в списке турезьпз, аргументы следует поменять Местами. Допустим, что типом т является класс Е111рзе, а типом ц — класс яессапд1е.
Тогда индекс типа т в списке турезьпз равен 1, а индекс типа ц в списке турезтпз равен О. Следовательно, аргументы типа е11(рзе и яестапд1е перед вызовом функции ехесысог:: Р( ге слелует поменять местами. В набор функциональных возможностей списков типов уже входит статический алгоритм ЕпдехОГ, возвращаЮщий позИЦИЮ тИПа В спИСКе.
Таким образом„сформулировать условие перестановки параметров довольно легко. 292 Часть П. Компоненты Во-первых, мы должны добавить новый шаблонный параметр, который позволяет определить, является ли диспетчер симметричным. Затем нужно добавить небольшой шаблонный класс характеристик хпчосас1оптга1сз, который либо переставляет ар- гументы, либо оставляет их на месте перед вызовом функции-члена ехесисог: е1ге. севр1асе < с1аьз ехесисог, Ьоо1 зуввесг(с, с1авз вазеьнз, с1азз турезьнз, с1аьз вазеябв = вазеьнз, с1авв турезябз = турезснз, сурепаве яези1стуре = чо1д > с1азз всас1со1зрассИег севр1асе <Ьоо1 зиарагоз, с1азз Еовеьнз, с1аьв Вовеибв> зсгисс хпчосас1оптгазсз ( зсас1с чо16 ооо1зрассЬ(вовеьЬМ 1Ьз, вовеянзй гнз, ехесисогб ехес) ( ехес.ебге(1Ьз„ гИз); ) ); севр1асе <с1аьз вовеьнз, с1азв еовеябз> зсгисс хпчосас1оптгазсз<тгие, вовеснз, Еовеябз> ( зсас1с чо1д ооо1зрассЬ(еовеьнзб 1Ьз, вовеянзй гИз, ехесисогй ехес) ( ехес.язге(гнз, 1Ьз); // меняем аргументы местами ) риЬ)з с: зсас(с чо1д оззрасснябв(вазесМВ 1Ьз, вазеянзй гнз, ехесисог ехес) ( 1У (меам* р2 = бупав1с саят<меана'>(вгнз)) епив ( змардгоз = зуввесгзс М хпдехог<неад, турезябз»::гези1с < хпдехог<вазеснз, турезснз>::гези1с ); суредег хпчосас1оптга1сз<зиарагоз, вазеснз, неао> са11тга1сз; гесигп са11тга1сз::ооо1зрассЬ(1Ьз, *р2); ) е1зе ( гехигп ЕСаС1соззрасснег<Ехеситог, Вазеьнз, ни11туре, вазеайз, та11>::о1зрасснянз( 1Ьз, гнз, ехес); Поддержка симметрии немного усложняет класс есас(со1зрассн, но намного облегчает его использование, 293 Глава 11.