Б. Страуструп - Дизайн и Эволюция C++. 2006 (1160775), страница 83
Текст из файла (страница 83)
Но если рЬ указывает на В (и только), то у <1упаю1с савс достаточно информации, чтобы верну~ь О, а всас1с саве должен «поверить» программисту, что рЬ указывает на О, и вернуть указатель на объект, предположительно принадлежащий классу (). Рассмотрим другой пример: с1азз О1: риЫ1с П ( /* ... */ ); с1авз О2: риЫгс В ( /* ... */ ); с1авв Х: риЫ1с 01, риЫ1С О2 ( /* ... */ ); уо1о д() ( П2* р62 = пен Х; Г(рд2); ) Здесь д ( ) вызовет й ( ), передав объект Б, который не является подобъектом (). Следовательно, с)упаппс савс правильно найдет «соседний» подобъект типа (), тогда как всасйс саво вернет указатель на какой-то неподходяший подобъект х. Насколько я помню, первым, кто обратил мое внимание на этот феномен, был Мартин О'Риордан.
с1авв Б; с1авв т; но1<) Г(гпг* рь, сваг* рс, Я* рв, Т* рс, ьпг 1) ( Б* рв2 = ге1псегргес саве<Я*>(рг]; Б* рвЗ = гегпгегргег сазг<Б*>(рг); сваг* рс2 = геьпсегртес саве<спас*>(рс)/ Япс* р12 = гегпсегргес савс<гпс*>(рс); Бпе 12 = геьпгегргег сазг<гпг>(рс); Япс* ртЗ = ге1пгегргес сазе<гас*>(1); Оператор ге1псегркес савс позволяет преобразовать указатель в любой другой, а также любой интегральный тип в любой указательный тип и наоборот. 14.3.3. Оператор те!птетртет саЯ| Нотация ге1пгегргег савг<т> (е) призвана заменить (т) е для преобразований вида сЬат* в Бис* и Бове с1авв* в упге1асес) с1аэв*, которые внутренне небезопасны и зачастую зависят от реализации.
При этом Бове с1азв и (Зпге1асед с1ава НЕ СвяэанЫ друГ С другом отяоШеНИеМ НаСЛедоваяия. По сути дела, оператор гейпсегргес саве возвращает значение, полученное в результате новой принудительной интерпретации своего аргумента. Например: ПИИИИИИ11 Приведение типов Все эти операции небезопасны, зависят от реализации.
Если только нужное преобразование не является по существу низкоуровневым и небезопасным, программисту лучше воспользоваться какими-то другими приведениями. В отличие от всасйс савс, на результаты работы гейпсегргес савс нельзя полагаться ни в одном из случаев за исключением приведения к исходному типу. Все остальные применения в лучшем случае непереносимы. Вот почему преобразования между указателем на функцию и указателем на член относятся к ге1псегргес саэс, а не к всасйс санс.
Например: уоЫ сЬпшр(сЬаг* р) ( *р = 'к'; гуредег чотб (*РР)(сопвг сЬаг*); РР рг; чоЫ о(сопвг сЬаг* рс) ( гьпшр(рс); // ошибка: неправильный тип аргумента рс = асЬпшр; // ошибка рг = всастс савс<РР>(аГЬпшр); // ошибка.' рг = ге1пгегргег савс<РР>(агЬпшр)/ // допускается, // но за последствия // отвечаете только вы // правильная работа не гарантируется! рб(рс) ) с1авв й ( /* ... */ с1авв В ( /* ... */ с1авв 0 : рпЬ11с й, рпнттс В ( /* ... */ ); чоЫ г (В' ( О* рд1 О* рб2 рЬ) = гетпсегргес саве<о*>(рЬ) = всасвс санс<В*>(рЬ)' Понятно, что присваивать р б указатель на ЬЬшар опасно, так как это действие в обход системы типов.
С помощью данной операции адрес константы может быть передан какой-то функции, которая сделает попытку ее модифицировать. Поэтому и нужно использовать приведение, причем именно оператор ге1псегргес савг.. Но для многих неожиданно, что вызов СЬшпр через р й все равно не гарантирован (в С++ так же, как и в С). Дело в том, что компилятору разрешено использовать разные соглашения о вызове для функций разных типов.
В частности, есть веские основания для того, чтобы константные и неконстантные функции вызывались по-разному. Заметим, что гейпсегргес савс не осуществляет навигацию по иерархии классов. Например: Новая нотация дпя приведения типов 1!ИИИИИИИ Здесь рс)1 и рс(2, как правило, получат различные значения. Прн вызове 14З.4. Оператор сопд1 саз1 Труднее всего было найти замену для старых приведений типов, чтобы корректно обрабатывался атрибут с оп в с. В идеале нужно было гарантировать, что констант- ность никогда не снимается сама собой. По этой причине каждый из операторов гейпсегргес савс,с)упатйс санс и всасъс сазс не изменял этотатрибут.
Нотация сопвс савс<т>(е) призвана заменить (т) е для преобразований, которым нужно получить доступ к данным с модификатором сопя с илн чо1а с 11е. Например: ехсегл "С" слаг* эсгслг(сваг*, слал); тл11пе солзс слаг* зсгспг(сслзс сваг* р, слаг с) ( гесигл зггслг(сопят сазг<слаг*>р, слаг с); В сопя С савС<Т> ( е ) тип аргумента Т должен совпадать с типом аргумента е во всем, кроме модификаторов сопвс и чо1ас11е.
Результатом будет то же значение, что у е, только его типом станет Т. Отметим, что результат отбрасывания сопзс для объекта, который первоначально был объявлен с этим модификатором, не определен (см. раздел 13.3). 14.3.4 1. Проблемы защиты сопзт В системе типов есть некоторые тонкости, которые открывают «дыры> в защите от неявного нарушения константности. Рассмотрим пример: солзс слег сс = 'а'; солэг слаг* рсс = асс; солвс слал** ррсс = ьрсс; чо1с(* рч = ррсс; // никакого приведения не нужно: (/ ррсс — не константный указатель, он (/ лишь указывает на таковой, но l/ константность исчезла( слег** ррс - (сваг**)рч( // указывает на рсс чей т() **ррс = 'х'; ) // теперь можем изменять константную переменную! г(леы П); рс(2 будет указывать на начало переданного объекта (), тогда как рс(1 — на начало подобъекга в в объекте ().
Операция ге1псегргес савс<т> (агд) почти также неудачна, каки (т) агу. Однако она более заметна в тексте программы, никогда не осуществляет навигации по классам и не отбрасывает атрибут сопвс. Для данной операции есть альтернативы, гейпсегргес санс — средство лля низкоуровневых преобразований, обычно зависящих от реализации, — и только! ИПИИИИИИИ Приведениетипов Однако оставление чойг) * небезопасным можно считать приемлемым, поскольку все знают — или, по крайней мере, должны знать, — что приведения из типа чоЫ* — зто хитрость. Подобные примеры приобретают практический интерес, когда вы начинаете строить классы, которые могут содержать много разных указателей (например, чтобы минимизировать объем сгенерированного кода, см.
раздел 15.5). Объединения (цпюп) и использование многоточия для явного подавления проверки типов — это тоже «дыры» в механизме защиты от неявного нарушения константности. Однако я предпочитаю систему, в которой есть несколько»прорех», той, которая вообще не предоставляет никакой защиты. Как и в случае с чоЫ*, программисты должны понимать, что объединения и отказ от контроля аргументов опасны, что их следует по возможности избегать, а применять с особой осторожностью и только в случае необходимости. 14.3.5. Преимущепва новых приведений типов Новые операторы приведения типов направлены на минимизацию и локализацию небезопасных и подверженных ошибкам приемов программирования.
В данном разделе рассматриваются ситуации, связанные с этой проблематикой (старые приведения типов, неявные сужающие преобразования и функции преобразования), н возможности преобразования существующего кода с учетом новых операторов приведения. 14.3.5.1. Старые приведения типов Вводя новый стиль приведения типов, я хотел полностью заменить нотацию (т) е, объявив ее устаревшим синтаксисом. В таком случае комите~ должен был предупредить пользователей о том, что нотацию (т) е могут исключить из следующего стандарта С++. Я видел прямые параллели между такой акцией и введением в стандарт АЮ1/150 С прототипов функций в стиле С++, когда неконтролируемые вызовы были объявлены устаревшими.
Однако предложение не нашло должного отклика у большинства членов комитета, поэтому очистка С++, по-видимому, никогда не произойдет. Важнее, однако, то, что новые операторы приведения дают некоторую возможность уйти от небезопасных конструкций, если есть мнение, что безопасность важнее обратной совместимости с С. Новый синтаксис приведения может быть также поддержан предупреждениями компилятора против использования старых приведений типов. Благодаря новым операторам приведения становится возможен более безопасный, но не менее эффективный стиль программирования. Очевидно, важность этого аспекта начнет возрастать по мере того, как будет повышаться общее качество кода, а новые инструментальные средства, ориентированные на безопасность типов, найдут широкое применение.
Это относится как к С++, так и к другим языкам. 14.3.5.2. Неявные сужающие преобразования Идея уменьшить число нарушений статической системы типов и сделать их по возможности очевидными лежит в основе всей работы над новым синтаксисом Новая нотация для приведения типов й! ВИИИИИЕН чо1с) г(с)тат с, впогг в, 1пг г) ( с++; в++; ) // результат может не поместиться в сваг // результат может не поместиться в впогл // может произойти переполнение Если запретить неявное сужение диапазона, то операции с+ а н в++ станут недопустимыми, поскольку объекты типа с)таг и в)гогс преобразуются в 1пс перед выполнением арифметических действий. Если требовать, чтобы сужающие преобразования всегда задавались явно, этот пример пришлось бы переписать так: чо1о г(с)заг с, в)гоге в, гпг г) ( с = всас1с савв<сваг>(с+1); с = вгаггс савг<впогг>(в+1); 1++; ) Я не надеюсь, что такая нотация приживется, если у нее нет какого-либо очевидного преимущества.