Б. Страуструп - Язык программирования С++. Специальное издание, 3-изд. Бином. 2004 (1160791), страница 102
Текст из файла (страница 102)
Приведение с(упат(с сая1 используется в тех случаях, когда правильность преобразования не может быть определена компилятором. В этом случае, бупатк сая1кТ*> ~э) смотрит на объект, на который указывает р (если указывает) Гели э го объект класса Т или он имеет единственный базовый класс типа Т, с!упптЫ саят возвращает указатель типа Т * на этот объект; в противном случае, возвращается ноль. Если значение р равно нулю, с(упит(с сая1<Т '> (р~ возвращает ноль.
Обратите внимание на требование, что преобразование должно производится в однозначно идентифицируемый объект. Можно сконструировать примеры, когда преобразование не удастся, и возвратится О, потому что объект, на которьш указывает р, имеет более одного подобъекта, представляю!цего базовые классы типа Т(см. й 15А.2). Для выполнения понижающего нли перекрестного приведения требуется, чтобы аргумент с(упит(с сая(был ссылкой или указателем на полиморфный тип. Например: с1аяяМУ я!Ыег риЫ!суза! я1Ыег( О полшчорфныи базовый класс // (Роа! я!Ыегимеет вир!пуальные функции) 465 15.4. Информация о типе на этапе выполнения Пунктирная линия означает смещение, которое позволяет найти начало объекта при наличии только указателя на полиморфный подобъект.
Ясно, что с(упит(с сав1люжно реализовать эффективно. Все, что для этого нужно — несколько сравнений объектов 1уре !и/о, представляющих базовые классы; дорогостоящий поиск или сравнение строк не требуется. 1Лспользование с!упат!с сов!только с полиморфными типами имеет смысл и с логической точки зрения. Если у объекта нет виртуальных функций, им нельзя безопасно манипулировать, не зная его конкретный тип.
Следовательно, должны быть приняты меры предосторожности, чтобы такой объект не оказался в контексте, в котором его тип неизвестен. Если его тип известен, нет необходимости в использовании с(упат!с сав1. Результирующий тип с(упат(с сап!не обязан быть полиморфным. Это позволяет нам «завернуть» конкретный тип в полиморфный с целью, скажем, передачи объекта системе ввода/вывода и возврата его обратно гсм. й 25А.1) с последующим его извлечением. Например: // базовый класс системы вводи/выводе е!вез!в оЬ1( и!есиа!Тв оЬТ с!иле() =0; с!авз !о басе: риЫЫ0а1е, рибйсТв вЬ! (); ио!с1/(гв вЬ!'рсо) ( Оа1е' рд = с!упатсе еазС<Ра1е'> (рсо); Приведение в поЫ' с помощью с!упит(с сав1 можно использовать для определения адреса начала объекта полиморфного типа. Например: ивЫ у (Рзв! Ьвх* рЬ,!>а1е' рс!) ( ио!<Орс!! =Йулати еазс ивЫ'> у>Ь); //привил»но ио!д*рпс2 = с!упит!с саз1<ивЫ"> (рд); //оисибка: 0а1е — не полилсорфный тип Это полезно только при взаимодействии с низкоуровневыми функциями.
15.4.1.1. Динамическое приведение ссылок Для осушествления полиморфного поведения доступ к объекту должен производиться через указатель или ссылку. При использовании с/упатсс сав1 с типами указателей О означал неуспешное преобразование. Такое поведение нежелательно и недопустимо прилсенительно к ссылкам.
Если результат является указателем, мы должны учитывать вероятность того, что он равен О, то есть указатель не указывает ни на какой объект. Следовательно, результат применения с(упит!с сав1 к указателю нужно всегда явно проверять. Для указателя р приведение с!упат(с сав1<Т *> (р) можно интерпретировать как вопрос: «Объект, на который указывает р, имеет тип Т?». С другов стороны, мы законно можем прелположить, что ссылка ссылается на некий объект. Следовательно, для ссылки г приведение с(упит!с сав1<Тй> (г) является 4бб Глава 15. Иерархии классов не вопросом, а утверждением: «Объект, на который ссылается г, имеет тип Т». Результат применения с(дпат!с саз! к ссылке неявно проверяется самой реализацией с!упат(с сааб Если операнд динамического приведения к ссылке не принадлежит ожидаемому типу, генерируется исключение Ьас( саз!.
Например: иоЫ! ((оа! Ьо*'р,!ио1 Ьокь,~) ( //р указьтает на некий?иа1 з!1йег? (! )«оп1 ЫЫег' В = йупат(с сазб уса! з(Ыег'» (р)) ( // использование(з е!зе ( //*р не ползунок (то есть не из семейства 1иа1 ИЫег! гоа! з1Ыег! 1з = йупатгс сазг<зоа! з(ЫеМ> (г); //г ссылается нп некий 1иа! зршеи // использование а Различие между результатами неуспешного динамического преобразования указателя и ссылки отражает фундаментальное отличие между самими указателями и ссылками.
Если пользователь хочет защититься от неудачных приведений к ссылке, необходимо предоставить подходящий обработчик. Например: ооЫд () ггу ( // аргументы передаются как объекты 1иа1 Ьох 1(нет ВВ сои! з!Ыег, "нет ВВ (иа1 з!Ыег); //аргументы передаются как ооьекты 1иа1 Ьох р (пего ВЬс)1а1, *нет ВЬдга!), сагсЬ (Ьпд сазг) ( //у 1«'.10 ) Первый вызовД)) завершится нормально, а второй — генерирует исключение Ьас! саз1, которое будет перехвачено в д (). Явное сравнение указателя с нулем может быть (а значит, время от времени — будет) пропущено. Гели вто вас беспокоит, можете написать преобразуюгцую функцию, которая генерирует исключение, а не возвращает 0 Я 15.8(1)) в с.чучае неудачного выполнения.
15.4.2. Навигация по иерархии классов Когда используется только одиночное наследование, класс совместно с его производными классами образует дерево с корнем в едннственнолс базовом классе. Это просто, но часто слишком ограничительно. При использовании множественного наследования не существует единственного корня.
Само по себе зто не слишком усложняет снтуацшо. Однако если класс появляется в иерархни более одного раза, нам следует проявлять некоторую осторожность при обращении к объекту или объектам, которые представляют зтот класс. 467 15.4. Информация о типе на этапе выполнения Гстественно, мы пытаемся конструировать иерархии настолько простыми, насколько нам позволяет наше приложение (но не проще).
Однако, создав нетривиальную иерархию, мы тут же сталкиваемся с необходимостью навигации по ней с целью нахождения класса с подходяшим интерфейсом. Эта потребность проявляется в двух аспектах. Иногда мы хотим явно назвать объект базового класса пли член базового класса; примерами могут служить ч 15.2.3 и З 15.2.4.1, В других случаях мы хотим получить указатель на объект, представляющий базовый илп производный класс объекта, при наличии указателя на завершенный объект или некоторый подобъект; примерами могут служить з 15А и з 15.4.1.
Рассмотрим способы навигации по иерархии классов с использованием прив4дений типа для получения указателя желаемого типа. В качестве иллюстрации имею- Шихся механизмов и правил их использования рассмотрим решетку классов, содержащую повторяюшийся базовый класс и виртуальный базовый класс: с!азз Сотропеп! рибйс о!с!ив! Бгогаб(е ( /' ... '/ ), с!азз Кесегоег риб(гс Сотропепг( /' ...
"/); с(азз Тгапзпипег риб(!с Сотропеп! ( /* ... '/); с!азз7(ас(го риЫ!с йесегоег,риЫ!с Тгапзтгггег(/'... '/), Или в графическом виде: 1сас(!о В этом примере объект 1тас((о содержит два подобъекта класса Сотропеп!. Следовательно, динамическое приведение пз 5!огаЫе в Сотропеп! внутри 1сас(!о неоднозначно и результатом будет О. Просто не существует способа распознать, какой Сотропеп! программист имел в виду: оо(с(б! (пал(!оЬ г) ( ЯгогаЫе* рз = 8г; 0- Сотропеп1* рс = с(упит!с сазсеСотропеп!"> (рз); //рс — 0 Эту неоднозначность в обшем случае нельзя обнаружить на этапе компиляции: оо(с! 62 (Бгогаб!е* рз) // рз можелп указывать на Сот ропепг, // а может и не указывать ( Сотропеп!'рс = с!упатгс спзт<Сотропепрь улз), Такое определение неоднозначности требуется только для виртуальных базовых классов.
В случае обычных базовых классов всегда существует единственный подобъект 468 Глава 15. Иерархии классов (либо его вообше не существует) данного понижающего приведения (то есть приведения в с~орону производного класса; 8 15.4). Аналогичная неоднозначность возникает при повышаюшем приведении (то есть приведения в сторону базового класса; 8 15А), и такие неоднозначности обнаруживаются на этапе компиляции. 15.4.2.1. Статическое идинамическое приведение Динамическое приведение с4упат(с сазгмохкет преобразовать полиморфный виртуальный базовый класс в производный или «братский» класс Я 15.4.1). Статическое приведение з(а11с саз1 (8 6.2.7) не анализирует объект, который оно приводит, поэтому оно не может этого сделать: оо1д д 1Кайо8 г1 ( // Кесесаег — обычный базовый класс для Кайо Кесесоег* ргес = йг; // правильно, не проверяется Кайо" рг = зГаГ1с сазГ<Кайо"> 1ргес1 О правильно, проверзется нп зтапе выполнения рг= дупатсс сазГ Кайо" > (ргес1 О 5согаие является виртуитьныь базовылг кл ассад для Кайо 51огаЫе*рз = 8г, (/ошибка: нельзя выполнить приведение // из виртуального базового кл ассп рг= з1апс саз1кйойо'> фз1; /,С правильно, прояеряется ни зтапе выполнения рг = дупат1с сазГкКайо*> 1рз1, Динамическое приведение требует наличия полиморфного операнда, потому что в неполиморфном объекте нет информации, которая может быть использована для нахождения объектов, для которых он представляет базовый класс.
В частности, объект типа с ограничениями на способ размещения в памяти, задаваемыми некоторыми языками, — такими как Рогггап или С вЂ” может использоваться в качестве виртуального базового класса. Для объектов таких типов доступна только статическая информация о тине. Однако информация, требуемая для определения типа на этапе выполнения, включает в себя информацию, необходимую для реализации с4упат(с сазй Почему может возникнуть потребность в использовании згагсс сазг при навигации по иерархии классов? При использовании с(длапис сазг дополнительные накладные расходы на этапе выполнения невелики (8 15.4.1).
Более важно то, что существуют миллионы строк кода, написанных до появления с(упапис сазб Этот код использует альтернативные способы обеспечения корректности преобразования, поэтому проверка осуществляемая с7упат(с саз( кажется избыточной. Однако такой код обычно написан с использованием приведений в стиле С Я 6.2.7), и поэтому в нем часто остаются скрытые опшбки. Там где это возможно, используйте более безопасное динамическое приведение.