Эта пара функций (по одной для указателей и ссылок) просто проверяет согласованность двух вариантов преобразования. Вы можете либо самостоятельно приспособить
checked_cast
для своих нужд, либо использовать имеющийся в библиотеке.
кроме того, это свидетельство плохого стиля программирования даже в том случае, когда применение такого преобразования вполне законно.
Обсуждение
Применение
const
— это дорога с односторонним движением, и, воспользовавшись этим спецификатором, вы не должны давать задний ход. Если вы отменяете
const
для объекта, который изначально был объявлен как константный, задний ход приводит вас на территорию неопределенного поведения. Например, компилятор может (и, бывает, так и поступает) поместить константные данные в память только для чтения (ROM) или в страницы памяти, защищенные от записи. Отказ от
const
у такого истинно константного объекта — преступный обман, зачастую караемый аварийным завершением программы из-за нарушения защиты памяти.
Даже если ваша программа не потерпит крах, отмена
const
представляет собой отмену обещанного и не делает того, чего от нее зачастую ожидают. Например, в приведенном фрагменте не происходит выделения массива переменной длины:
void Foolish(unsigned int n) {
const unsigned int size = 1;
const_cast<unsigned int&>(size) = n; // Не делайте так!
char buffer[size]; // Размер массива
// ... // все равно равен 1
}
В С++ имеется одно неявное преобразование
const_cast
из строкового литерала в
char*
:
char* weird = "Trick or treat?";
Компилятор молча выполняет преобразование
const_cast
из
const char[16]
в
char*
. Это преобразование позволено для совместимости с API в стиле С, хотя и представляет собой дыру в системе типов С++. Строковые литералы могут размещаться в памяти только для чтения, и попытка изменения такой строки может вызвать нарушение защиты памяти.
Исключения
Преобразование, отменяющее
const
, может оказаться необходимым для вызова функции API, некорректно указывающей константность (см. рекомендацию 15). Оно также полезно, когда функция, которая должна получать и возвращать ссылку одного и того же типа, имеет как константную, так и неконстантную перегрузки, причем одна из них вызывает другую:
Возраст не всегда означает мудрость. Старое преобразование типов в стиле С имеет различную (и часто опасную) семантику в зависимости от контекста, спрятанную за единым синтаксисом. Замена преобразования
типов в стиле С преобразованиями С++ поможет защититься от неожиданных ошибок.
Обсуждение
Одна из проблем, связанных с преобразованием типов в стиле С, заключается в том, что оно использует один и тот же синтаксис для выполнения несколько разных вещей, в зависимости от таких мелочей, как, например, какие именно заголовочные файлы включены при помощи директивы
#include
. Преобразования типов в стиле С++, сохраняя определенную опасность, присущую преобразованиям вообще, имеют четко документированное предназначение, их легко найти, дольше писать (что дает время дважды подумать при их использовании), и не позволяют незаметно выполнить опасное преобразование
reinterpret_cast
(см. рекомендацию 92).
Рассмотрим следующий код, в котором
Derived
— производный от базового класса
Base
:
extern void Fun(Derived*);
void Gun(Base* pb) {
// Будем считать, что функция Gun знает, что pb в
// действительности указывает на объект типа Derived и
), то компилятор имеет всю необходимую информацию о размещении объекта, чтобы выполнить все необходимые действия по корректировке указателя при преобразовании от
Base
к
Derived
. Но если автор
Gun
забыл включить соответствующий файл определения, и функции
Gun
видно только предварительное объявление класса
Derived
, то компилятор будет полагать, что
Base
и
Derived
— несвязанные типы, и интерпретирует биты указателя
Base*
как биты указателя
Derived*
, не делая никаких коррекций, которые могут диктоваться размещением объекта в памяти!
Коротко говоря, если вы забудете включить определение класса, то ваш код может аварийно завершиться без видимых причин, при том что компилятор не сообщил ни об одной ошибке. Избавимся от проблемы следующим способом:
extern void Fun(Derived*);
void Gun(Base* pb) {
// Если мы гарантированно знаем, что pb на самом деле
// указывает на объект типа Derived:
// Преобразование в стиле С++
Derived* pd = static_cast<Derived*>(pb);
// В противном случае следует использовать
// = dynamic_cast<Derived*>(pb);
Fun(pd);
}
Теперь, если у компилятора недостаточно статической информации об отношениях между
Base
и
Derived
, он выведет сообщение об ошибке, вместо того чтобы автоматически применить побитовое (и потенциально опасное) преобразование