Бьерн Страуструп. Язык программирования С++. Специальное издание (2011) (1004033), страница 103
Текст из файла (страница 103)
Если это вас беспокоит, вы можете написать преобразующую функцию, которая вместо возврата нуля генерирует исключение (815.8[1]). 15.4.2. Навигация по иерархиям классов Когда используется лишь одиночное наследование, класс совместно с его базовыми классами образуют дерево с корнем в единственном базовом классе. Это просто, но часто слишком ограничительно. Когда используется множественное наследование, единственного корня не существует. Само по себе это не усложняет суть дела. Однако если класс появляется в иерархии более одного раза, нам следует проявлять осторожность при обращении к объектам такого класса. Естественно, мы пытаемся конструировать иерархии настолько простыми, насколько нам позволяет делать это само приложение. Но если иерархия получилась нетривиальной, перед нами встает проблема навигации по этой иерархии с целью выявления класса с подходящим интерфейсом.
Эта потребность проявляется в двух аспектах. Иногда нам нужно явно поименовать обьекг базового класса или член базового класса (см. 815.2.3 и 815.2.4.1). В иных случаях нам бывает необходимо получить указатель на объект, представляющий базовый или производный класс объекта, при наличии указателя на полный объект или некоторый подобьекг (см. 815.4 и 415.4.1). Рассмотрим способы навигации по иерархии классов с применением приведений типа для получения указателя желаемого типа.
Для иллюстрации самого механизма и регулирующих его правил рассмотрим классовую иерархию, содержащую как повторяющийся базовый класс, так и виртуальный базовый класс: е1азз Сотропепг: риЬВе Изака! ВзогаЫе ( 1*... *У ) ! с(азз Весе!гег: риЫ(с Сотропеаг ( У *; .. * У ) ! е1азз Тгапзт1лег: риЫ(е Сотропеп! ( У*...
*1 1 ! е)азз Вайо: риЫ!е Вееегкег, риЫ!с Тгапзт!Вег ( У*... *У ) ! или в графическом виде: 15.4. Механизм ))ТТ! (йпп-Т)гпе Туре)п(оггпабоп) 499 Сотропепг Сотропепг Кесеп ег Тгапзтглег Кайо юЫйл (КаЖоь г) БгогаЫе* рз = ью уу ... Сотропелг* рс = йупатгс сам<Сотропет*> )рз); ) Урс» О Эта неоднозначность в общем случае на этапе компиляции не выявляется: юЫ 62 (бгогаЫе* рз) ( Сотропепг* рс = йупатгс сазг<Сотропепг*> (рз) уу ...
) Урз может указгавать на Сотропепг, а может и него Такое выявления неоднозначности на этапе выполнения требуется лишь для виртуальных базовых классов. Для обычных базовых классов всегда при понижающих преобразованиях существует единственный подобъект (915.4), в то время как для повышающих преобразований и здесь возникает аналогичная неоднозначность, обнаруживаемая на этапе выполнения. 19.4.2.1. Операции а1а1)с саа1 и с)упал))с саа1 Операция 4аатгс саге может преобразовывать полиморфный виртуальный базовый класс в производный класс или в класс того же уровня иерархии («братский» класс) (915.4.1).
Операция агапе сазе (96.2.7) не анализирует объект, тип которого она приводит, и поэтому она не может этого сделать: юЫК(КаЖоь г) 1 Кесепег* ргес = ью УУЯесеьмег обычный базовый класс для Кайо Кайо» рг = лабе сам<Кайо*> (ргес) г УУ ок, без проверки рг = ггупатгс сам<Кайо*> (ргес); .Чо )г, проверка на этапе выполнения В этом примере объект типа Кайо содержит два подобьекта класса Сотролелг. Следовательно, динамическое приведение операцией Нулатгс слег типа огогайе в тип Сотролет в рамках объекта Кайо неоднозначно и вернет нуль. Просто нет способа узнать, какой именно СопЧюлелг запрашивает программист: Глава 15.
Иерархии классов У 3(огаЫе - виртуальный базовый класс для Йаауо УУ еггог: приведение из Мггиа! базе невозможно УУ од проверка на этапе выполнения КгогаЫе* рз = ьг; рг = ма((с сам<Йайо*> (рз); рг = аупат!с сов!<Кайо*> (рз); ) Кайо* !'(ыоЫ* р) ( БгогаЫе* рз = з(анс сазг<бгогаЫе*> (р); ге<игп йупат(с сам<Йайо*> (рз); ) УУ под ответственность програлаииста Обе операции приведения — Купат!с саз! и таас сиз!, учитывают модификатор сола и уровни доступа.
Например: с(ат Ь)зегз: рпыа(е лег<реглан> ( У* ... *У ); ыогг(/'( (Узегз* ри, сопл! КесеГыег* рсг) ( аванс саз«лег<Реглан»" (ри); ауиатгс сам<нег<Реглан»" (ри); УУ еггог: нарушение доступа У еггог: нарушение доступа Бганс сазг<Йесе(ыег*> (рсг); Йупат!с сазг<Кесепег*> (рсг); УУ еггог: невозможно "снять" сонм УУ еггог: невозможно "снять" сопл( Кесе!ыег* рг = сопя! саз«Кесебыег*> (рсг); УУо(г уу ... ) Невозможно осуществить преобразование к закрытому базовому классу, а для преодоления действия модификатора сопл! (или ыо7ап1е) требуется операция сопя! сам (в6.2.7). И даже в этом случае, результат надежен, лишь если объект не был с самого начала объявлен как сопи (или из!а!!7е) (в10.2.7.1).
Операция в(упит!с саз! требует полиморфного операнда, потому что в неполиморфном объекте нет информации, которую можно использовать для того, чтобы решить, является ли указанный класс базовым для него. Объекты типов, имеющие ограничения на размещение в памяти, накладываемые языками гопгап или С, могут использоваться в качестве виртуальных базовых классов. Для таких объектов имеется лишь статическая информация о типе. Информация, необходимая для определения типа на этапе выполнения, включает в себя информацию, необходимую для реализации операции Купат!с сазу. А зачем вообще может потребоваться операция мабс сам в контексте навшации по классовым иерархиям? Ведь дополнительные накладные расходы по применению операции 4папис сазг невелики (в15.4.1).
Однако существуют миллионы строк кода, написанные до появления в(упагп!с сазг. Они опираются на альтернативные способы проверки корректности преобразований, так что проверка операцией в(зоват!с сазг кажется избыточной. В типичном случае этот код использует приведения в стиле языка С (56.2.7), и поэтому в нем остаются тонкие и трудноуловимь|е ошибки.
В общем, где возможно используйте существенно более безопасные приведения операцией Купат!с сазу. Компилятор не имеет представления о том, что адресуется указателем типа ыоЫ*. Отсюда следует, что операция Купат!с сазг, которой требуется «заглянуть в объекты лля выявления его типа, не может преобразовывать тип ыоЫ*. Здесь-то и требуется мапо саз!. Например: ) 5.4. Механизм йТТ( (йцп-Т(гпе Туре )п(огпэа()оп) 501 15.4.3. Конструирование и уничтожение классовых объектов Объект класса — это нечто большее, чем просто область памяти (э4.9.6). Объект класса создается поверх «сырой памяти» («газ« щепюгу») с помощью конструкторов, и эта память становится снова «сырой» после отработки деструкторов.
Создание протекает снизу вверх, а уничтожение идет сверху вниз; при этом объект класса является таковым в той мере, в какой он был создан, и до того, как был уничтожен. Этот факт отражен в правилах для йТТ1, обработки исключений (514.4.7) и виртуальных функций.
Крайне неразумно полагаться на порядок создания и уничтожения объектов, но порядок этот отражается на вызовах виртуальных функций, работе операций Иупапз(с саят и гуреЫ (В15.4.4) в момент, когда объект создан не полностью. Например, если конструктор класса Сотропепг из иерархии (В15.4.2) вызывает виртуальную функцию, будет вызвана версия классов ЯогаЫе или Союропепб но не версии классов Кесерег, Тгапвт(ггег или Кайо. В этой фазе конструирования объект еше не является объектом Кайо, а является лишь частично созданным объектом.
Лучше всего избегать вызова виртуальных функций на этапе создания или уничтожения объектов. 15.4.4. Операция 1уре!д и расширенная информация о типе Операция в(упапз1с саегудовлетворяет большинство потребностей в информации о типе на этапе выполнения программы. Особо важно, что она гарантирует, что использующий эту операцию код корректно работает с классами, производными от некоторого указанного программистом класса.
Таким образом, Иупапз(с сазгдемонстрирует гибкость и расширяемость, свойственную виртуальным функциям. Тем не менее, бывают случаи, когда нужно узнать точный тип объекта. Например, потребовалось узнать имя класса объекта или его расположение в памяти. Для этого имеется операция зуреЫ, возвращающая объект, содержащий имя операнда этой операции. Если бы гуреЫ() была функцией, ее объявление выглядело бы примерно следующим образом: с(авв |уре 1пуоз сопя( гуре 1п)оь зуреИИуре пате) гвгоы () ) У псевдообьявление сопя( гуре Ы)ос гуреЫ (еяргеввюп ) агом (Ьа«( гуреЫ); Ф1 псевдообьявление То естыуреЫ() возвращает ссылку на тип из стандартной библиотеки, называемый гуре 1пуо (определен в файле <(уре(пуо>). Для операндов гуре пате (имя типа) нли ехргезз(оп (выражение) операцией возвращается ссылка на объект гуре 1пто, содержащий собственно тип или тип выражения.
Чаше всего операция гуреЫ() вызывается для объектов, адресуемых указателем или ссылкой: гоЫГ(ЯЬареа г, БЬаре* р) ( гуреЫ (г); (г тип объекта, на который ссылается г гуреЫ(«р); (гтип обьекта, на которыи указывает р гуреЫ(р); утин указателя (здесь это БЬаре*) ) Если значение операнда-указателя полиморфного типа равно пулю, операция гуреЫ() генерирует исключение ЬаИ гуреЫ. Если операнд операции гуреЫ() имеет ббг 15. Иерархии классов неполиморфный тип или он не является 1ча!це, то результат определяется во время компиляции без вычисления выражения операнда. Часть класса <уре 1пгд (не зависящая от конкретной реализации) выглядит следуюшим образом: с<ам <уре <пуо ( риЫ<с: ч1г<иа! -гуре п<(о() < //пслиморфный //можно сравнивать //упорядочение // имн типа Ьоо< орега<ог== (сопя« уре !пупа) сопя<; Ьоо1 орега<ог! = (сопя« уре !пуоя) сопя« Ьоо! Ьеуоге (сопя« уре !п<оя) сопя« сопя< слагь пате() сопя« рг!ча<в: <уре !п3о(сопи<уре (пуоь) < <уре !пуоя орега<ог= (сопя« уре !п(оь) < // ...
// предотвращает копирование // предотвращает копирование Мпс!иле с<уре(п)о > иоЫ е (Сотропеп<* р) ( сои< «<уреЫ(*р) .па<не() ) Символьное представление имени класса является системнозависимым. Эта С-строка располагается в системной области памяти, так что программист не должен освобождать ее операцией <!е!его (1. 15.4.4.1.
Расширенная информация о типе Как правило, выявление точного типа объекта является лишь первым шагом получения более детальной информации о типе и его возможностях. Функция Ьеуоге() помогает сортировать объекты типа гуре 1пуо. Нет никакой связи мелсду отношениями упорядочения функцией Ьеуоге() и отношениями наследования. Не гарантируется, что есть только один объект <уре 1пуо для каждого типа в системе. На самом деле, в системах с библиотеками динамической компоновки трудно избежать дублирования объектов <уре 1пго. Следовательно, нужно применять операцию == для выявления эквивалентности объектов <уре 1пУо, а не сравнивать между собой значения указателей на такие объекты.