Б. Страуструп - Дизайн и Эволюция C++. 2006 (1160775), страница 78
Текст из файла (страница 78)
В частности, суре Ы (*Ьр) возвращает объект, позволяющий программисту задавать вопросы о типе объекта, на который указывает Ьр. В данном случае нас интересует только, совпадает ли тип этого объекта стипомс(Ьох м зсг. Это простсшций из вопросов, которые можно задать, но, как правило, он некорректен. Спрашиваем-то мы, чтобы узнать, можно ли безопасно воспользоваться той или иной возможностью производного класса.
А чтобы применить ее, нам нужен указатель на объект этого производного класса. В примере выше мы использовали приведение типа в строке, следующей за проверкой. Но ведь обычно нас интересует, удастся лн безопасно выполнить приведение. Этот запрос можно дать непосредственно, воспользовавшись оператором дупаглйс сазс: чогб пу бел(сьа1од Ьох* Ьр) ( (бЬох ы всг* бЬр =- дулам(с савс<сЬох н всг*>(Ьр)) ( У! воспользоваться СЬр е1во ( /! работать с *Ьр, как с обычным диалоговым окном ) Оператор дупалгйс саэс<т*> (р) преобразует свой операнд в нужный тип Т", если *р действительно принадлежит типу Т или типу класса, производного от Т.
В противном случае значение дупаглус саэС<Т*> (р) равно О. Есть несколько причин для объединения проверки и приведения типа в одну операцию: о динамическое приведение не допускает несоответствия между приведением и проверкой; о пользуясь данными, которые содержатся в объектах с информацией о типе, можно выполнять приведение к типам, которые не полностью определены в области действия проверки; з при таком жс условии допустимо выполнять приведение от виртуального базового класса к производному (см. раздел 14.2.2.3); З статическое приведение типов не дает правильных результатов во всех случаях (см. раздел 14.3.2.1). В большинстве ситуаций можно обойтись оператором с1упаыус сазс.
Думается, что это важнейшая часть механизма КТТ1 и именно на нее пользователи должны обратить особое внимание. Идентификация типа во время исполнения ЯфффффффЩ Оператор с(упапп'.с сазе можно использовать и для приведения к ссылочпым типам. Если приведение к типу ссылки заканчивается неудачно, возникает исключение Ьас( сазс. Например: уоьп ту Гсс(61а1оц Ьоха Ь( ( с(Ьох н вота оЬ = с(упааьс саво<дЬох я вогэ>(Ь(; воспользоваться с(Ь Я пользуюсь приведением к ссылке, когда хочу проверить предположение о ес типе и считаю, что ошибочное предположение — это сбой в программе.
Если же нужно выбрать один из нескольких возможных вариантов, то использую приведение к указателю и проверяю результат. Не помню, когда'я пришел к выводу, что приведение с проверкой типа — лучший способ идентификации типа во время исполнения, коль скоро в языке есть явная поддержка этого. Данную идею мне подал кто-то из специалистов компании Хегох РАКС. Предложение заключалось в том, чтобы обычные приведения выполняли проверку. Как отмечается в разделе 14.2.2.1, этот вариант сопровождался большими затратами и проблемами с совместимостью, но я осознал, что слегка отличный синтаксис приведения может свести к минимуму неправильное использование, к которому располагает механизм переключения по типу вроде предложения 1мяРест в Яшп!а. 14.2.2.1. Синтаксис Спор о том, как должен выглядеть оператор дупалхс саэ с, имел синтаксические аспекты.
Приведения типов в наибольшей степени провоцируют ошибки при программировании на языке С++. И синтаксически они принадлежат к числу наиболее неудачных особенностей. Естественно, по мере возможности я хотел: о устранить приведения вообше; о сделать их безопасными; о предложить такой синтаксис приведений, который с очевидностью показывал бы, что используется небезопасная информация; ш предложить альтернативы приведению типов, чтобы не возникало желания прибегать к ним. Вывод о том, что 3-е и 4-е сочетания реализуемы, а 1-е и 2-е — нет, отражает с(упат1с санс.
Рассматривая первое сочетание, мы отметили, что ни в каком языке, поддерживаюшем системное программирование, нельзя полностью отказаться от приведения типов. Определенные его формы нужны даже для эффективной поддержки численных расчетов. Поэтому следовало лишь постараться минимизировать употребление приведений и по возможности безопасно определить их поведение. Исходя из этой предпосылки, мы с Ленковым разработали предложение, в котором унифицировались динамические и статические приведения с использованием НИИИИИИИ Приведение типов старого синтаксиса. Поначалу зто казалось хорошей мыслью, но прн ближайшем изучении вскрылнсь некоторые проблемы; а динамические и обыкновенные неконтролируемые приведения типов — это принципиально различные операции.
Динамическое приведение лля получения результата исследует объект и может вернуть ошибку во время выполнения, если что-то не получается. Обыкновенное приведение выполняет операцию, которая зависит исключительно от участвующих типов и на которую не влияет значение объекта (если нс считать проверку на нулевой указатель). Обыкновенное приведение типов не может завершиться с ошибкой, оно просто возвращает новое значение. Использование одного и того же синтаксиса для динамических и обыкновенных приведений затрудняет истинное понимание происходящего; о если динамические преобразования синтаксически не выделены, то их довольно трудно найти в тексте программы (например, с помощью утилиты дгер, стандартной для системы ПХ1Х); о если динамические преобразования синтаксически не выделены, то невозможно поручить компилятору отвергнуть их некорректное использование; он просто должен выполнять то приведение, которое возможно для участвующих в операции типов.
Если же различие есть, то компилятор может выдать ошибку при попьпке динамического приведения для объектов, которые не поддерживают проверок во время исполнения; д семантика программы, в которой есть обыкновенные приведения, может измениться, если всюду, где это возможно, будут применяться динамические приведения. Примерами служат приведения к неопределенным классам и приведения внутри иерархий с множественным наследованием (см.
раздел 14.3.2). Такое изменение вряд ли оставит семантику всех разумных программ неизменной; ш затраты на проверку типов будут ненулевыми даже для старых программ, где корректность приведения и так тщательно проверялась другими средствами; а предлагавшийся способ «отключения проверки» путем приведения к нов* и обратно не был бы стопроцентно надежным, поскольку в некоторых случаях могла измениться семантика. Возможно, все такие случаи — зто отклонения, но из-за необходимости разбираться в коде процесс отключения проверки приходилось бы осуществлять вручную, что вело бы к ошибкам. Мы были также против любой техники, которая могла увеличивать число неконтролируемых приведений типов в программах; а если часть приведений сделать безопасными, это повысило бы доверие к приведению вообще; но нашей конечной целью было уменьшить число всех и всяческих приведений (включая и динамические).
После долгих споров была найдена формулировка: «Должно ли в идеальном языке быть более одной нотации для преобразования типов?» Да, если операции, принципиально различные семантически, различаются в языке и синтаксически. Поэтому мы оставили попытки приспособить старый синтаксис приведений.
Идентификация типа во время испопнения;ЦффффЩД Мы подумывали о том, стоит ли объявить прежний синтаксис устаревшим, заменив его чем-то аналогичным Спескес)<Т*>(р); // преобразование р к тллу т* с проверкол во время // исполнения Ппспескес)<т*>(р); // неконтролируемое преобразование р к типу Т* В результате все преобразования бросались бы в глаза, так что исчезли бы проблемы, связанные с поиском традиционных привслений в программах па языках С н С++. Кроме того, синтаксически все преобразования следовалп бы тому жс образцу <т*>, что и шаблоны типов (см. главу 15).
Рассуждения в этом направлении привели нас к альтернативному синтаксису, изложенному в разделе 14.3. Какое-то время популярна была нотация (эТ*) р, поскольку она напоминает традиционный синтаксис приведения (т*) р. Но некоторым пользователям данная нотация и не нравилась по той же причине, а другие считали сс <слишком непонятной>. К тому же я обнаружил в этой нотации существенный изъян. При использовании конструкции ( эт*) р многие забывали бы вставить знак '.. Но тогда приведение, которое должно было быть отпоситсльно безопасным и контролируемым, превратилось бы в свою противоположность. Например: 1б (дЬох и вбт1лд* р = (г)Ьох и вккупп*)П) // динамическое // преобразование ( // *П есть <)Ьох и вкгьлд ) Слишком уж просто позабыть о э и тем самым сделать комментарий ложным.
Заметим, что глобальный поиск старых приведений не защитит от такой ошибки, а тем из нас, кто привык работать на С, особенно легко допустить ее, а потом не найти при чтении кода. В качестве самого многообещающего рассматривался такой вариант: (у(гсоа1 Т*)Р Он сравнительно легко распознается как человеком, так и инструментальпымн средствами, слово чугспа1 указывает на логическую связь с классами, имекнпиып виртуальные функции (полиморфными типами), а общий вид конструкции напоминает традиционные приведения. Однако многие пользовагели сочли его <ели)иком непонятнымн а те, кому не нравились старые приведения типов, отнеслись к такому синтаксису враждебно.
Я легко согласился с такой критикой: интуиция подсказывала, что синтаксис с(упапьс сазс лучше подходит к С++ (в этом со мной были согласны многие из тех, кто имел солидный опыт работы с шаблонами). Мне также казалось, что синтаксис с1упапйс саэс более удачный и будет способствовать вытеснению прежнего (см. раздел 14.3). 14.22.2. Примеры непользования динамических приведений Введение в язык идентификации типа во время исполнения разделяет объекты на две категории: ПИИИИИВ6:: Приведение типов 14.2.2.3.
Приведение из виртуальных базовых классов Появление оператора с)упап1с санс позволило решить одну проблему. С помощью обычного приведения нельзя осуществить преобразование из виртуального базового класса в производный: объект не содержит для этого достаточно информации — см. раздел 12А.1. Однако информации, необходимой для идентификации типа во время исполнения, вполне достаточно и для того, чтобы реализовать динамическое приведение из полиморфного виртуального базового класса. Поэтому старое ограничение снимается: с1авв В ( /* ...