Б. Страуструп - Дизайн и Эволюция C++. 2006 (1160775), страница 82
Текст из файла (страница 82)
Кроме того, оператор '. = не так неудачен, как приведения типов, поэтому провоцирует на нсправпльное употребление. Разрешив объявления в условиях (см. раздел 3.11.5.2), я сделал возможным использование дупат1с савв в стиле этой альтернативы: уогб Т(в *рЫ ( 1Т (р* рд2 = дупаш(с саек<о*>(рЫ ) ( // // правильно: контролируется 14.3. Новая нотация для приведения типов згагьс саег<Т>(е) // 'хорошие" приведения типов геьпгегргес савс<Т>(е) !/ приведения, даюшие значения, которые для // безопасного использования пушно привести // к исходному типу // отбрасывание сопзг полег савс<т>(е) В данном разделе анализируются проблемы, свойственпыс старым приведениям типов, и описывается синтез нового средства.
Во многом определения операторов появились в процессе бурных споров среди членов рабочей группы по расширениям. Особенно конструктивные предложения внесли Дэг Брюк, Джор ри Шварц и Эндрю Кениг. Новые операторы приведения были одобрены па заседании комитета в Сан-Хосе в ноябре 1993 г. Синтаксически и семантически приведения типов — одна нз самых неудачных особенностей С и С+<-.
Поэтому поиск других вариантов не прекращался. Неявные преобразования аргументов в объявлениях функций (см. раздел 2.6), 1паблоцы (см. раздел 14.2.3) и ослабление правил перегрузки для виртуальных функций (см. раздел 13.7) — все это позволяет устранить некоторые виды приведений, С другой стороны, оператор г)упаппс па в с (см. раздел 14.2.2) в отдельных случаях — более безопасная альтернатива старым приведениям. Оц и натолкнул на мысль пойти подругому пути — вычленить логически различные применения приведений типов и поддержать их с помошью операторов, похожих на с)упаш(с сазс: ШИИИИИИ11 Приведениетипов Поскольку объем книги ограничен, здесь рассматривается только самая большая трудность — указатели.
Проработка остальных вопросов: арифметических типов, указателей на члены, ссылок и тщ. — оставлена как упражнение читателю. 14.3.1. Недостатки старык приведений типов Выражение (т) ехргдаст(за очень немногими исключениями) значение типа 'г, тем или иным способом полученное на основе значения ехрг. Допускаю, что для этого потребуется иная интерпретация битов ехрг, могут также произойти сужение или расширение диапазона значений, арифметические операции пад адресами для навигации по иерархии классов, отключение атрибута сопзс или ыо1 ..11е и др.
Видя перед собой изолированное выражение с приведением тина, пользователь не в состоянии определить, что имел в виду его автор. Например: сопел Х* рс = пеы Х; // РУ = (У*) Рс; Хотел ли программист получить указатель на тип, никак не связанный с Х? Или убрать атрибут сопел? Или то и другое одновременно? Может быть, он намеревался получить доступ к классу у, базовому для х? Более того, безобидное, на первый взгляд, изменение объявления способно без всяких предупреждений полностью изменить смысл выражения, например: с1азз Х: рпЫьс А, рпЫ1с В ( /* ... '/ ); чоыв т (Х Рх) ( ((в*)рх)->я()/ // лызылается и нз класса в Рх->В::Р()/ // более явный н, значит, лучыий способ ) о трудны для понимания, поскольку предоставляют одну и ту же нотацию для различных слабо связанных между собой операций; о провоцируют ошибки, так как почти любая комбинация типов имеет какуюто допустимую интерпретацию; о с трудом отыскиваются в исходном тексте как вручную, так и с помощью простых утилит; о усложняют грамматику С и С++.
Изменим определение класса х так, чтобы в больше не являлся для него базовым классом, и смысл (в*) рх станет совершенно другим, а компилятор не сможет обнаружить ошибку. Помимо связанных со старыми приведениями семантических проблем, неудобна и нотация. Она близка к минимальной и использует только скобки, то есть синтаксическую конструкцию, которая в С и так употребляется чрезмерно часто.
Поэтому пользователю трудно найти в программе все приведения. И с помощью инструментального средства типа дгер осуществлять поиск нелегко. К тому же синтаксис приведения типов — одна из основных причин усложнения грамматического разбора программы на С+ч-. Итак, старые приведения типов: Новая нотация для приведения типов Яфффффф$П(П Новые операторы приведения призваны распределить функциональность старых по разным категориям. Они должны уметь выполнять все тс же операции, что и прежние операторы, иначе будут приведены доводы в пользу использования старых и в дальнейшем.
Было найдено только одно исключение: с помошыо старого синтаксиса можно приводить от производного класса к его закрытому базовому классу. Я не вижу оснований для такой операции — она опасна и бесполезна. Невозможно получить доступ к полностью закрытому представлению объекта, да это и не нужно. Тот факт, что старые приведения типов позволяли добраться до части представления закрытого класса, — досадная случайность.
Например: с1азз Р : рцЬ)(с А, рг1чаее В ( рг1часе; ьлс ш; // ); чо!«) Г(Р* ( В* рвт ро) // г() - ме член и не дружественная функция Р (В*)рби // получаем доступ к закрытому базовому О классу В. Не годится! зсасьс сдлс<В*>(рб); // ошибка: нет доступа к закрытому // классу. Верно! Если исключить манипуляции с рс( как с указателем на неинициализированную память, то функция г ( ) не получит доступ к Р:: и. Таким образом, новые операторы приведения закрывают «дыру» в правилах доступа н обеспечивают большую логическую непротиворечивость. Длинные названия и подобный шаблонам синтаксис новых приведений пе внушает доверия некоторым пользователям. Но, может, это и к лучшему, поскольку одна из целей, которые мы ставили, включая данные средства в язык, — напомнить, что приведение типов — дело рискованное.
Кстати, наибольшее недовольство по данному поводу высказывали те, кто пользуется С++ преимушсственно как диалектом С и полагает, что приводить типы нужно как можно чаще. Нотация кажется странной и це привыкшим к шаблонам. с1авзВ( /* ... "/ ); с1алл Р : рцЬ11с В ( /* ... '/ чо1с) 1(В* рЬ, Р* рб) 14З.2.
Оператор итаМс садт нотация зсас1с сазе<с>(е) призвана заменить (т) е для преобразований от Вазе* (указатель ца базовый класс) к Рет1чег1* (указатель на производный класс). Такие преобразования не всегда безопасны, но часто встречаются и могу~ разумно определяться даже в отсутствие проверок во время исполнения. Например: ШЗВИИИИВ Приведение типов ( о* рб2 = всаььс саля<о*>(рь); // раньше писали (о*)рЬ В* рЬ2 = ягагьс саля<В*>(рЬ); // // безопасное преобразование с1аяя Х; // Х вЂ” неполный тип с1азя У; О т — неполный тип лоьб г(Х* рх) ( т* р = (у*)рх; // разрешено, но опасно р = вгагьс саяг<т*>(рх)/ // ошибка: Х и У не определены ) Тем самым устраняется еще один источник ошибок. Если вам нужно приведение к неполным типам, пользуйтесь оператором ге1псегргеС санс (см.
раздел 14.3.3), чтобы ясно показать, что вы не пытаетесь осушествлять навигацию по иерархии, или оператором г)упалз1с саво (см. Раздел 14.2.2). 14.3.2Л. Статические и динаиические приведения Применение ясас(с саяе и с)упаш1с саво к указателям на классы приводит к навигации по иерархии классов. Однако всаь(с саят опирается только на статическую информацию и поэтому может работать некорректно.
Рассмотрим пример: с1аяя В ( /' ... */ ): с1аяя О : риЬ)1с В ( /* ... */ ); со1б 1(В* рЬ) Можно представлять себе ясас1с саве как явное обрашение операции неявного преобразования типов. Если на время забыть о том, что всас(с саве сохраняет константность, то он позволяет выполнить я->т при условии, что преобразование т->В может быть выполнено неявно. Отсюда следует, что в большинстве случаев результат вьасус санс можно использовать бездальнейшего приведения. В этом отношении он отличается от ге(псегргес саяс (см.
раздел 14.3.3). Кроме того, в сап(с санс вызывает преобразования, ко~орые можно выполнить неявно (например, стандартные и определенные пользователем). В противоположность с)упаЫс саяг для применения япаг1с саяс крЬне требуется никаких проверок во время исполнения. Объект„на который указывает рЬ, может и не принадлежать к классу 1), тогда результаты использования *рс12 не определены и, возможно, пагубны. В отличие от старых приведений типов указательные и ссылочные типы должны быль полными.
Это означает, что попытка использовать ясас1с саяе для преобразования к указателю на тип илн от него в случае, когда компилятор еще не встречал объявления этого типа, приведет к ошибке. Например: ИИИИИИИЕИ Новая нотация дпя приведения типов ( П* ро1 = аупав1с сазг<0*>(рЫ; П* р<)2 = згаг1с савг<п*>(рЫ; Если в действительности рЬ указывает на О, то рд1 и рс)2 получают одно и то же значение. Так же, как и в случае, когда рЬ= = О.