Стандарты программирования на С++. 101 правило и рекомендация - [84]

Шрифт
Интервал

при отладке и >static_cast в окончательной версии (см. [Stroustrup00]):

>template To checked_cast(From* from) {

> assert(dynamic_cast(from) ==

>  static_cast(from) && "checked_cast failed" );

> return static_cast(from);

>}


>template To checked_cast(From& from) {

> typedef tr1::remove_reference::type* ToPtr; // [C++TR104]

> assert(dynamic_cast(&from) ==

>  static_cast(&from) && "checked_cast failed");

> return static_cast(from);

>}

Эта пара функций (по одной для указателей и ссылок) просто проверяет согласованность двух вариантов преобразования. Вы можете либо самостоятельно приспособить >checked_cast для своих нужд, либо использовать имеющийся в библиотеке.

Ссылки

[Dewhurst03] §29, §35, §41 • [Meyers97] §39 • [Stroustrup00] §13.6.2 • [Sutter00] §44

94. Избегайте преобразований, отменяющих >const

Резюме

Преобразование типов, отменяющее >const, может привести к неопределенному поведению, а кроме того, это свидетельство плохого стиля программирования даже в том случае, когда применение такого преобразования вполне законно.

Обсуждение

Применение >const — это дорога с односторонним движением, и, воспользовавшись этим спецификатором, вы не должны давать задний ход. Если вы отменяете >const для объекта, который изначально был объявлен как константный, задний ход приводит вас на территорию неопределенного поведения. Например, компилятор может (и, бывает, так и поступает) поместить константные данные в память только для чтения (ROM) или в страницы памяти, защищенные от записи. Отказ от >const у такого истинно константного объекта — преступный обман, зачастую караемый аварийным завершением программы из-за нарушения защиты памяти.

Даже если ваша программа не потерпит крах, отмена >const представляет собой отмену обещанного и не делает того, чего от нее зачастую ожидают. Например, в приведенном фрагменте не происходит выделения массива переменной длины:

>void Foolish(unsigned int n) {

> const unsigned int size = 1;

> const_cast(size) = n; // Не делайте так!

> char buffer[size];                   // Размер массива

> // ...                               // все равно равен 1

>}

В С++ имеется одно неявное преобразование >const_cast из строкового литерала в >char*:

>char* weird = "Trick or treat?";

Компилятор молча выполняет преобразование >const_cast из >const char[16] в >char*. Это преобразование позволено для совместимости с API в стиле С, хотя и представляет собой дыру в системе типов С++. Строковые литералы могут размещаться в памяти только для чтения, и попытка изменения такой строки может вызвать нарушение защиты памяти.

Исключения

Преобразование, отменяющее >const, может оказаться необходимым для вызова функции API, некорректно указывающей константность (см. рекомендацию 15). Оно также полезно, когда функция, которая должна получать и возвращать ссылку одного и того же типа, имеет как константную, так и неконстантную перегрузки, причем одна из них вызывает другую:

>const Object& f(const Object&);


>Object& f(Object& obj {

> const Object& ref = obj;

> return const_cast(f(ref)); // преобразование

>}                                    // возвращаемого типа

Ссылки

[Dewhurst03] §32, §40 • [Sutter00] §44

95. Не используйте преобразование типов в стиле С

Резюме

Возраст не всегда означает мудрость. Старое преобразование типов в стиле С имеет различную (и часто опасную) семантику в зависимости от контекста, спрятанную за единым синтаксисом. Замена преобразования типов в стиле С преобразованиями С++ поможет защититься от неожиданных ошибок.

Обсуждение

Одна из проблем, связанных с преобразованием типов в стиле С, заключается в том, что оно использует один и тот же синтаксис для выполнения несколько разных вещей, в зависимости от таких мелочей, как, например, какие именно заголовочные файлы включены при помощи директивы >#include. Преобразования типов в стиле С++, сохраняя определенную опасность, присущую преобразованиям вообще, имеют четко документированное предназначение, их легко найти, дольше писать (что дает время дважды подумать при их использовании), и не позволяют незаметно выполнить опасное преобразование >reinterpret_cast (см. рекомендацию 92).

Рассмотрим следующий код, в котором >Derived — производный от базового класса >Base:

>extern void Fun(Derived*);


>void Gun(Base* pb) {

>// Будем считать, что функция Gun знает, что pb в

>// действительности указывает на объект типа Derived и

>// хочет передать его функции Fun

>Derived* pd = (Derived*)pb; // Плохо: преобразование

>Fun(pd);                    // в стиле С

>}

Если функция Gun имеет доступ к определению >Derived (например, при включении заголовочного файла >derived.h), то компилятор имеет всю необходимую информацию о размещении объекта, чтобы выполнить все необходимые действия по корректировке указателя при преобразовании от >Base к >Derived. Но если автор >Gun забыл включить соответствующий файл определения, и функции >Gun видно только предварительное объявление класса >Derived, то компилятор будет полагать, что >Base и >Derived — несвязанные типы, и интерпретирует биты указателя


Рекомендуем почитать
Pro Git

Разработчику часто требуется много сторонних инструментов, чтобы создавать и поддерживать проект. Система Git — один из таких инструментов и используется для контроля промежуточных версий вашего приложения, позволяя вам исправлять ошибки, откатывать к старой версии, разрабатывать проект в команде и сливать его потом. В книге вы узнаете об основах работы с Git: установка, ключевые команды, gitHub и многое другое.В книге рассматриваются следующие темы:основы Git;ветвление в Git;Git на сервере;распределённый Git;GitHub;инструменты Git;настройка Git;Git и другие системы контроля версий.


Java 7

Рассмотрено все необходимое для разработки, компиляции, отладки и запуска приложений Java. Изложены практические приемы использования как традиционных, так и новейших конструкций объектно-ориентированного языка Java, графической библиотеки классов Swing, расширенной библиотеки Java 2D, работа со звуком, печать, способы русификации программ. Приведено полное описание нововведений Java SE 7: двоичная запись чисел, строковые варианты разветвлений, "ромбовидный оператор", NIO2, новые средства многопоточности и др.


MFC и OpenGL

В книге рассказывается история главного героя, который сталкивается с различными проблемами и препятствиями на протяжении всего своего путешествия. По пути он встречает множество второстепенных персонажей, которые играют важные роли в истории. Благодаря опыту главного героя книга исследует такие темы, как любовь, потеря, надежда и стойкость. По мере того, как главный герой преодолевает свои трудности, он усваивает ценные уроки жизни и растет как личность.


Симуляция частичной специализации

В книге рассказывается история главного героя, который сталкивается с различными проблемами и препятствиями на протяжении всего своего путешествия. По пути он встречает множество второстепенных персонажей, которые играют важные роли в истории. Благодаря опыту главного героя книга исследует такие темы, как любовь, потеря, надежда и стойкость. По мере того, как главный герой преодолевает свои трудности, он усваивает ценные уроки жизни и растет как личность.


Обработка событий в С++

В книге рассказывается история главного героя, который сталкивается с различными проблемами и препятствиями на протяжении всего своего путешествия. По пути он встречает множество второстепенных персонажей, которые играют важные роли в истории. Благодаря опыту главного героя книга исследует такие темы, как любовь, потеря, надежда и стойкость. По мере того, как главный герой преодолевает свои трудности, он усваивает ценные уроки жизни и растет как личность.


Питон — модули, пакеты, классы, экземпляры

Python - объектно-ориентированный язык сверхвысокого уровня. Python, в отличии от Java, не требует исключительно объектной ориентированности, но классы в Python так просто изучить и так удобно использовать, что даже новые и неискушенные пользователи быстро переходят на ОО-подход.