Чтение онлайн

на главную - закладки

Жанры

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

Александреску Андрей

Шрифт:

 const Object& ref = obj;

 return const_cast<Object&>(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
— несвязанные типы, и интерпретирует биты указателя
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
, он выведет сообщение об ошибке, вместо того чтобы автоматически применить побитовое (и потенциально опасное) преобразование
reinterpret_cas
t (см. рекомендацию 92).

Преобразования в стиле С++ могут защитить корректность вашего кода в процессе эволюции системы. Пусть, например, у вас есть иерархия с корнем в

Employee
, и вам надо определить уникальный идентификатор
ID
для каждого объекта
Employee
. Вы можете определить ID как указатель на сам объект
Employee
. Указатели однозначно идентифицируют объекты, на которые указывают, и могут сравниваться на равенство друг другу — что в точности то, что нам и надо. Итак, запишем:

typedef Employee* EmployeeID;

Employee& Fetch(EmployeeID id) {

 return *id;

}

Пусть вы кодируете часть системы с данным дизайном. Пусть позже вам требуется сохранять ваши записи в реляционной базе данных. Понятно, что сохранение указателей — не то, что вам требуется. В результате вы изменяете дизайн так, чтобы каждый объект имел уникальный целочисленный идентификатор. Тогда целочисленный идентификатор может храниться в базе данных, а хэш-таблица отображает идентификаторы на объекты

Employee
. Теперь
typedef
выглядит следующим образом:

typedef int EmployeeID;

Employee& Fetch( EmployeeID id ) {

 return employeeTable_.lookup(id);

}

Это корректный дизайн, и вы ожидаете, что любое неверное употребление

EmployeeID
должно привести к ошибке времени компиляции. Так и получается, за исключением следующего небольшого фрагмента:

void TooCoolToUseNewCasts(EmployeeID id) {

 Secretary* pSecretary = (Secretary*)id; // Плохо:

 // ... // преобразование в стиле С

}

При использовании старой инструкции

typedef
преобразование в стиле С выполняет
static_cast
, при новой будет выполнено
reinterpret_cast
с некоторым целым числом, что даст нам неопределенное поведение программы (см. рекомендацию 92).

Преобразования в стиле С++ проще искать в исходных текстах при помощи автоматического инструментария наподобие

grep
(но никакое регулярное выражение
grep
не позволит выловить синтаксис преобразования типов в стиле С). Поскольку преобразования очень опасны (в особенности
static_cast
для указателей и
reinterpret_cast
; см. рекомендацию 92), использование автоматизированного инструментария для их отслеживания — неплохая идея.

Поделиться:
Популярные книги

Курсант: назад в СССР

Дамиров Рафаэль
1. Курсант
Фантастика:
попаданцы
альтернативная история
7.33
рейтинг книги
Курсант: назад в СССР

Чужая семья генерала драконов

Лунёва Мария
6. Генералы драконов
Фантастика:
фэнтези
5.00
рейтинг книги
Чужая семья генерала драконов

Пышка и Герцог

Ордина Ирина
Фантастика:
юмористическое фэнтези
историческое фэнтези
фэнтези
5.00
рейтинг книги
Пышка и Герцог

Имперский Курьер. Том 5

Бо Вова
5. Запечатанный мир
Фантастика:
попаданцы
аниме
фэнтези
5.00
рейтинг книги
Имперский Курьер. Том 5

Имя нам Легион. Том 7

Дорничев Дмитрий
7. Меж двух миров
Фантастика:
боевая фантастика
рпг
аниме
5.00
рейтинг книги
Имя нам Легион. Том 7

Возвышение Меркурия

Кронос Александр
1. Меркурий
Фантастика:
героическая фантастика
попаданцы
аниме
5.00
рейтинг книги
Возвышение Меркурия

Законы Рода. Том 11

Андрей Мельник
11. Граф Берестьев
Фантастика:
юмористическое фэнтези
аниме
фэнтези
5.00
рейтинг книги
Законы Рода. Том 11

Измена. (Не)любимая жена олигарха

Лаванда Марго
Любовные романы:
современные любовные романы
5.00
рейтинг книги
Измена. (Не)любимая жена олигарха

Вираж бытия

Ланцов Михаил Алексеевич
1. Фрунзе
Фантастика:
героическая фантастика
попаданцы
альтернативная история
6.86
рейтинг книги
Вираж бытия

Отмороженный 11.0

Гарцевич Евгений Александрович
11. Отмороженный
Фантастика:
боевая фантастика
рпг
попаданцы
фантастика: прочее
фэнтези
5.00
рейтинг книги
Отмороженный 11.0

История "не"мощной графини

Зимина Юлия
1. Истории неунывающих попаданок
Фантастика:
попаданцы
фэнтези
5.00
рейтинг книги
История немощной графини

Крестоносец

Ланцов Михаил Алексеевич
7. Помещик
Фантастика:
героическая фантастика
попаданцы
альтернативная история
5.00
рейтинг книги
Крестоносец

Газлайтер. Том 15

Володин Григорий Григорьевич
15. История Телепата
Фантастика:
боевая фантастика
попаданцы
5.00
рейтинг книги
Газлайтер. Том 15

Призыватель нулевого ранга

Дубов Дмитрий
1. Эпоха Гардара
Фантастика:
аниме
фэнтези
фантастика: прочее
5.00
рейтинг книги
Призыватель нулевого ранга