Бьерн Страуструп. Язык программирования С++. Специальное издание (2011) (1004033), страница 102
Текст из файла (страница 102)
Операция дупат!с сов!как раз и предназначена для этого. Например, пусть система вызывает функцию ту еуепт Ьап«!!ег() с указателем на обьект (окно) типа ВВм!пе!он, в котором произошла какая-то активность. Затем я мог бы реагировать на событие вызовом функции г!о воте141ня() семейства классов 1уа1 Ьох гоЫ ту е»епг Ьапд!ег (ВВннпдоы* ры) )Т(1»а1 Ьох* рЬ = Мулат!с сае!«1»а1 Ьох" > )рге) ) дукаэывает рв на 1»а! Ьоху рЬ->йо еоте)Ыпя (); е1«е ( /! Ооре) неожиданное событие ) Происходящее можно условно объяснить так, что г!упат!с сазг переводит с языка реализации системы графического интерфейса пользователя на язык нашего приложения.
Очень важно отметить, что в примере отсутствует точный тип объекта. Объект лишь должен иметь тип одного из классов семейства 1га1 Ьох, скажем Ба! в!Ыег, реализованный одним из видов ВВв!пе!ом, скажем ВВв1Ыег. Нет никакой необходимости в знании точного типа объекта в этом взаимодействии между «системой» и приложением. Любой интерфейс существует лишь для открытия важных аспектов взаимодействия. А несущественные детали любой хороший интерфейс скрывает. Графически, работу операции рЬ = дунет!с сае(<1»а1 Ьох" > (ры) можно изобразить следующим образом 1»а! Ьох ~" " " " рЬ ры " " """т ВВннпдоы 1га1 е1Ыег ВВе!Ыег ВВ па1 е!Ыег Стрелки от рм и рЬ символизируют указатели на передаваемый объект, а остальные стрелки отражают отношения наследования между различными частями передаваемого объекта.
Получение и использование информации о типе объекта на этапе выполнения программы обычно называют механизмом ктт1 (гип-лте )уре Гпуоппа)1оп). Приведение типа от базового класса к производному обычно называют понижоюи)ии приведением (доипсаэ1) из-за традиции рисовать классовые иерархии свер- ) 5.4. Механизм КТТ( (йцп-Т(п)е Туре )п(оггпабоп) ху вниз. Соответственно, приведение от производного класса к базовому называют повышающим приведением (ирсаз!). Преобразования между классами одного уровня иерархии называют перекрестным приведением (сгоззсаз!). 15.4.1. Операция с(упап))с сав1 Операция !!упит!с сов! имеет два операнда: тип, заключенный в угловые скобки, и указатель (или ссылка), заключенный в круглые скобки.
Сначала рассмотрим вариант с указателем: йупат!е еалг<Т" > (р) Если р имеет тип Т' или лг*, где Тявляется базовым классом для 1г, результат будет таким же, как при простом присваивании р указателю типа Т . Например: е!ат ВВ Ыа! з!Ыег: риЬВе 1га! в!Ыег, ргогеегей ВВз!Ыег ( В... ): гоИ 1(ВВ Ыа! зЬЫег* р) ( 1га! зЬЫег* р!1 = р: ,У ой 1га! ИЫег* р!2 = лупинис еазг<1га! з!Ыег*> (р); ~У ой ВВз!Ыег' рЬЬ! = р; У еггог; ВВз!Ыег — защищенный базовый класс ВВз!Ыег" рЬЬ2 = йупат!е еазг<ВВзЬЫег'> (р);,У ой: рЬ02 станет О ) Рассмотренный пример большого интереса не представляет. Тем не менее, все равно приятно, что операция Ыупат!с сав! не допускает случайных нарушений уровня доступа к закрытым и зашишенным базовым классам.
Свое основное предназначение операция е(упит!с сал! демонстрирует в случаях, когда компилятор не может выяснить, корректно преобразование типов или нет. Тогда операция а)тат!е сам<Т*> (р) обращается к объекту, на который указывает р. Если это объект типа Т или типа, у которого имеется уникальный базовый класс Т„то операция г!упит(с еал! возврашает указатель типа Т' на этот объект; в противном случае возвращается нуль.
Если значение р равно пулю, то Иупат!с салг<Т'> (р) возвращает нуль. Обратите внимание на необходимость преобразования к уникально идентифицируемому обьекту. Можно сконструировать примеры, когда преобразование невозможно и возврашается нуль из-за того, что объект, на который показывает р, содержит несколько подобъектов базового класса Т(см. з)5.4.2). Для выполнения понижающего илн перекрестного приведения нужно, чтобы аргумент операции Иупат!с сатг был ссылкой или указателем на полиморфный тип. Например: е!азв Ыу з!Ыег:риЫ!с 1га! зййег В базовый класс - полиморфный (имеет виртуоль, ф-ии) 496 Глава (5.
Иерархии классов с1аев Му ааге: риЫЫ 1Уа!е ( // ... // базовый класс — непслиморфный (нет виртуал». ф-ий) »оЫ а (1»а1 Ьох* рЬ, Эа!е* рй) ( Му з1Ыег* рг!! = йупат!с сал!<Му з(Ыег*> (рЬ); //о/с Му аа!е* р«(2 = Иупат!с сам<Му аа!е*> (ра); //еггог: Юа(е - не пслииорфный тип Требование полиморфности указателя облегчает компилятору реализацию !(упит!с сиз!, ибо в этом случае легко найти место для хранения необходимой информации о типе объекта.
В типичном случае специальный объект с информацией о типе прикрепляется к полиморфному объекту посредством добавления указателя на этот специальный объект к виртуальной таблице класса 52.5.5). Например: Му ЫЫет г »р!г »й!г гуре 1п(еи В' нМу з(Ыег" ( Ьазез !уре Гтуог »1»а! з1Ыегн с!авв 1о оЬ1' ( »!г!иа1 1о оЬ)* с(опе() //базовый класс длн системы ввода)вывода с!авз 1о йа!е: риЬцс1)а!е, риЫ(с!о оЬ! (); Пунктирная линия изображает смещение, позволяющее найти начало полного объекта при наличии лишь указателя на полиморфный подобъект. Ясно, что Нупат(с саз! можно реализовать весьма эффективно. Все, что для этого требуется, это несколько сравнений объектов !уре !аУо, представляющих базовые классы (и никаких дорогостоящих табличных поисков или сравнений строк).
Ограничение диапазона действия операции !(упат(с саа! лишь полиморфными типами оправдано и с логической точки зрения. Действительно, если у объекта отсутствуют виртуальные функции, им невозможно безопасно пользоваться без знания его типа. Поэтому его нужно аккуратно применять лишь в контекстах, в которых его тип известен.
А если тип объекта известен, то нет необходимости в операции «!упит!с саз!. Результирующий тип операции !!упит(с саз! не обязан быть полиморфным. Это позволяет нам «завернуть» (итар) конкретный тип в полиморфный с целью, скажем, его передачи системе ввода/вывода (925.4.1), а позднее «развернуть» (цпвгар) этот конкретный тип. Например: ) 5.4. Механизм РТТ( (Рпп-Т~)пе Туре Иогп)а(юп) 497 юЫГ(1о оЬ1* р!о) ( Ра!е* рй = йупатк саз!<Разе* > (р!о) уу ... Применение йуеитс саз! для приведения к типу юЫ* можно использовать, чтобы вычислить адрес начала объекта полиморфного типа.
Например; гоЫ а(1»а1 Ьох* рЬ, Ра(е* рй) ( го!й* рй1 = йупатк саи<юЫ*> (рЬ) ! УУой юЫ* рЖ = йупатк сазг<гоЫ*> (рй); УУ еггог: Ра(е - не полииорфный тип Это полезно лишь при взаимодействии с очень низкоуровневыми функциями. 19.4.1.1. Применение с(упепз(с саят к ссылкам Для обеспечения полиморфного поведения доступ к объекту должен выполняться через указатель или по ссылке. Когда йупапис сам используется с указателями, возврат нуля означает невозможность приведения. Но к ссылкам все это абсолютно неприменимо. Работая с указателями, мы всегда должны учитывать возможность его равенства нулю (зто означает, что указатель не адресует никакого объекта). То есть результат работы операции йупатгс саз! над указателями должен всегда проверяться явным образом, а саму операцию йупапис сазг<Т'>(р) можно трактовать как вопрос: «Действительно ли адресуемый указателем р объект имеет тип Т1».
С другой стороны, мы можем твердо рассчитывать на то, что ссылка действительно указывает на некоторый объект. Следовательно, йупат)с сиз!<Та> (з ) для ссылки г рассматривается уже не как вопрос, а как утверждение: «Обьект, на который ссылается г, имеет тип Т». Результат операции йупапис сазт тестируется в этом случае самой системой, и если ссылка имеет неправильный (несовместимый с заявленным) тип, то генерируется исключение Ьай саьт.
Например: гогй1(1»а! Ьох* р, 1»а! Ьохз г) ( а" (1»а1 з!Ыег* и = йупат!с сазг<1»а1 з(Ыег*> (р) ) У?указывает ли р на !»а! з1Ыег? ( УУ используем и ) е(зе ( УУ»р это не ползунок ) 1»а! з1!йегз Й = йупатк сои<1»а1 зрЫегз> (г); У?г ссылается на 1»а! з1Ыег( УУ используем и ) Различие в поведении операции йупат(с сам в ее работе над указателями и ссылками отражает фундаментальный факт различия между самими указателями и ссылками, Глава 15.
Иерархии классов 498 Если пользователю нужен надежный код в случае, когда з(упагп!е сап применяется к ссылкам, нужно перехватывать и обрабатывать исключение Ьаг~ сазг. Например: коме() ( ггу ( Т(пен ВВ Гка) з(Ыег, *пею ВВ !аа! з!Ыег); гУ аргумекты передаются как !га( Ьох Т(пею ВВг(!а1, *пеп ВВсаа1) ! УУ аргументы передаются как !га! Ьох ) еагея (Ьад еаз1) УУ з!410 ( уу ... ) ) Первый вызову(') завершится нормально, в то время как второй вызовет исключение Ьаз( сазз, которое будет перехвачено и обработано. Явные проверки на нуль в случае работы с указателями могут быть случайно пропущены.