|
Программирование >> Полиморфизм без виртуальных функций в с++
старого синтаксиса. Поначалу это казалось хорошей мыслью, но при ближайшем изучении вскрылись некоторые проблемы: □ динамические и обыкновенные неконтролируемые приведепия типов - это принципиально различные операции. Динамическое приведение для получения результата исследует объект и может вернуть ошибку во время выполнения, если что-то не получается. Обыкновенное приведение выполняет операцию, которая зависит исключительно от участвуюпщх типов и на которую пе влияет значение объекта (если не считать проверку па нулевой указатель). Обыкновенное приведение типов пе может завершиться с ошибкой, оно просто возврашает новое значение. Использование одного и того же синтаксиса для динамических и обыкновенных приведений затрудняет истинное понимание происходяшего; □ если динамические преобразования синтаксически не выделены, то их довольно трудно найти в тексте программы (например, с помошью утилиты grep, стандартной для системы UNIX); □ если динамические преобразования синтаксически не выделены, то невозможно поручить компилятору отвергнуть их некорректное использование; он просто должен выполнять то приведение, которое возможно для участвующих в операции типов. Если же различие есть, то ко.мпилятор может выдать ошибку при попытке динамического приведения для объектов, которые не поддерживают проверок во время исполнения; □ семантика программы, в которой есть обыкновенные приведения, может измениться, если всюду, где это возможно, будут применяться динамические приведения. Примерами служат приведения к пеопределеппым классам и приведения внутри иерархий с множественным наследованием (см. раздел 14.3.2). Такое изменение вряд ли оставит семантику всех разумных программ неизменной; □ затраты на проверку типов будут ненулевыми даже для старых программ, где корректность приведения и так тшательно проверялась другими средствами; □ предлагавшийся способ отключения проверки путем приведения к void* и обратно не был бы стопроцентно надежным, поскольку в некоторых случаях могла измениться семантика. Возможно, все такие случаи - это отклонения, но из-за необходимости разбираться в коде процесс отключения проверки приходилось бы осуществлять вручную, что вело бы к ошибкам. Мы были также против любой техники, которая могла увеличивать число неконтролируемых приведений типов в программах; □ если часть приведений сделать безопасными, это повысило бы доверие к приведению вообше; но нашей конечной целью было уменьшить число всех и всяческих приведений (включая и динамические). После долгих споров была найдена формулировка; Должно ли в идеальном языке быть более одной нотации для преобразования типов? Да, если операции, принципиально различные семантически, различаются в языке и синтаксически. Поэтому мы оставили попытки приспособить старый синтаксис приведений. Мы подумывали о том, стоит ли объявить прежний синтаксис устаревшим, заменив его чем-то аналогичным Checked<T*>(р); преобразование р к типу Т* с проверкой во времй исполнения Unchecked<T*>(р); неконтролируемое преобразование р к типу Т* В результате все преобразования бросались бы в глаза, так что исчезли бы проблемы, связанные с поиском традиционных приведений в программах иа языках С и C-1-I-. Кроме того, синтаксически все преобразования следовали бы тому же образцу <Т*>, что и шаблоны типов (см. главу 15). Рассуждения в этом направлении привели нас к альтернативному синтаксису, изложенному в разделе 14.3. Какое-то время популярна была нотация ( ?Т* ) р, поскольку она напо.ми-нает традиционный синтаксис приведения (Т* ) р. Но некоторым пользователям данная нотация и не нравилась по той же причине, а другие считали ее слишком непонятной . К тому же я обнаружил в этой нотации существенный изъян. При использовании конструкции (?т*)р .многие забывали бы вставить знак ?. Но тогда приведение, которое должно было быть относительно безопасным и контролируемым, превратилось бы в свою противоположность. Например: if (dbox w string* р = (dbox w string*)q) динамическое преобразование *q есть dbox w string Слишком уж просто позабыть о ? и тем самым сделать комментарий ложны.м. Заметим, что глобальный поиск старых приведений не защитит от такой ошибки, а тем из нас, кто привык работать на С, особенно легко допустить ее, а потом не найти при чтении кода. В качестве са.мого многообещающего рассматривался такой вариант: (virtual Т*)р Он сравнительно легко распознается как человеком, так и инструментальными средствами, слово virtual указывает на логическую связь с классами, имеюпщ.ми виртуальные функции (полиморфными типами), а общий вид конструкции напоминает традиционные приведения. Однако многие пользователи сочли его слишком непонятным , а те, ко.му не нравились старые приведения типов, отнеслись к такому синтаксису враждебно. Я легко согласился с такой критикой: интуиция подсказывала, что синтаксис dynamic cast лучше подходит к С-1-+ (в это.м со мной были согласны многие из тех, кто имел солидный опыт работы с шаблонами). Мне также казалось, что синтаксис dynamic cast более удачный и будет способствовать вытеснению прежнего (см. раздел 14.3). 14.2.2.2. Примеры использования динамических приведений Введение в язык идентификации типа во время исполнения разделяет объекты на две категории: □ те, с которыми ассоциирована информация о типе, так что их тип можно определить во время исполнения почти независимо от контекста; □ объекты, для которых это утверждение неверно. Почему? Мы не може.м идентифицировать во вре.мя исполнения тип объектов встроенных типов (папри.мер, int или double), поскольку это привело бы к не-приемле.мо.му расходу времени и памяти, а также породило проблемы сов.мести.мости с моделью раз.мещения объектов в па.мяти. То же самое относится и к объек-та.м простых классов, и к структура.м в стиле С. Следовательно, первая граница проходит .между объекта.ми классов с виртуальными функциями и классов без таковых. Для первых можно легко предоставить идентификацию типа во время исполнения, для вторых - нет. Кроме того, классы с виртуальными функциями часто называют полиморф-ны.\ш, и только их объектами можно безопасно манипулировать через базовый класс. Под слово.м безопасно я здесь пош1маю то, что язык гарантирует использование объекта лишь в соответствии с его типом. Ра.зумеется, некоторые програм-.vnicTbi могут продемонстрировать, что в особых случаях такого рода манипуляции с неполиморфными классами не нарушают систему типов. Поэтому с учето.м задач программирования представляется естественным обеспечить идентификацию типа во время исполнения только для полиморфных типов. Поддержка RTTI для других типов сводится просто к ветвлению по полю типа. Конечно, язык не должен препятствовать и тако.му стилю программирования, по нет необходимости усложнять язык только для того, чтобы поддержать это явно. Практика показывает, что предоставление RTTI исключительно для полиморфных типов вполне приемлемо. Однако пользователи часто не понимают, какие объекты полиморфны и можно ли применять к ним дина.мическое приведение типов. К счастью, ко.мпилятор выдаст сообшение об ошибке, если догадка программиста окажется неверной. Я долго и настойчиво искал приемлемый способ явно сказать: Этот класс поддерживает RTTI (вне зависимости от того, есть в нем виртуальные функции или нет) , - но не нашел такого, на который стоило бы тратить силы. 14.2.2.3. Приведение из виртуальных базовых классов Появление оператора dynamic cast позволило решить одну проблему. С помошью обычного приведения нельзя осушествить преобразование из виртуального базового класса в производный: объект не содержит для этого достаточно информации - см. раздел 12.4.1. Однако информации, необходимой для идентификации типа во время исполнения, вполне достаточно и для того, чтобы реализовать дина.мическое приведение из полиморфного виртуального базового класса. Поэтому старое ограничение снимается: class В {/*... */ virtual void f{); }; class V { /* ... */ virtual void g(); }; class X { /* нет виртуальных функций */ }; class D : public B, public virtual V, public virtual X { . . .
|
© 2006 - 2024 pmbk.ru. Генерация страницы: 0
При копировании материалов приветствуются ссылки. |