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