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

ЖАНРЫ

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

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

Шрифт:

• Тип

int
занимает как минимум 16 битов.

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

• Размер

int
не равен ни 32 битам, ни какому-либо иному фиксированному размеру.

• Указатели и целые числа не всегда имеют один и тот же размер и не могут свободно преобразовываться друг в друга.

• Размещение класса в памяти не всегда приводит к размещению базового класса и членов в указанном порядке.

• Между членами класса (даже если они являются POD) могут быть промежутки

в целях выравнивания.

• 

offsetof
работает только для POD, но не для всех классов (хотя компилятор может и не сообщать об ошибках).

• Класс может иметь скрытые поля.

• Указатели могут быть совсем не похожи на целые числа. Если два указателя упорядочены и вы можете преобразовать их в целые числа, то получающиеся значения могут быть упорядочены иначе.

• Нельзя переносимо полагаться на конкретное размещение автоматических переменных в памяти или на направление роста стека.

• Указатели на функции могут иметь размер, отличный от размера указателя

void*
, несмотря на то, что некоторые API заставляют вас предположить, что их размеры одинаковы.

• Из-за вопросов выравнивания вы не можете записывать ни один объект по произвольному адресу в памяти.

Просто корректно определите типы, а затем читайте и записывайте данные с использованием указанных типов вместо работы с отдельными битами, словами и адресами. Модель памяти С++ гарантирует эффективную работу, не заставляя вас при этом работать с представлениями данных в памяти. Так и не делайте этого.

Ссылки

[Dewhurst03] §95

92. Избегайте

reinterpret_cast

Резюме

Как гласит римская пословица, у лжи короткие ноги. Не пытайтесь использовать

reinterpret_cast
, чтобы заставить компилятор рассматривать биты объекта одного типа как биты объекта другого типа. Такое действие противоречит безопасности типов.

Обсуждение

Вспомните: Если вы лжете компилятору, он будет мстить (Генри Спенсер).

Преобразование

reinterpret_cast
отражает представления программиста о представлении объектов в памяти, т.е. программист берет на себя ответственность за то, что он лучше компилятора знает, что можно и что нельзя. Компилятор молча сделает то, что вы ему скажете, но применять такую грубую силу в отношениях с компилятором — последнее дело. Избегайте каких-либо предположений о представлении данных, поскольку такие предположения очень сильно влияют на безопасность и надежность вашего кода.

Кроме того, реальность такова, что результат применения

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

Исключения

Некоторые низкоуровневые специфичные для данной системы программы могут заставить вас применить

reinterpret_cast
к потоку битов, проходящих через некоторый порт, или для преобразования целых чисел в адреса. Используйте такое небезопасное преобразование как можно реже и только в тщательно скрытых за абстракциями функциях, чтобы ваш код можно было переносить с минимальными изменениями. Если вам требуется преобразование между указателями несвязанных типов, лучше выполнять его через приведение к
void*
вместо непосредственного использования
reinterpret_cast
,
т.е. вместо кода

T1* p1 = ... ;

T2* p2 = reinterpret_cast<T2*>(p1);

лучше писать

T1* p1 = ...;

void* pV = p1;

T2* p2 = static_cast<T2*>(pV);

Ссылки

[С++03] §5.2.10(3) • [Dewhurst03] §39 • [Stroustrup00] §5.6

93. Избегайте применения

static_cast
к указателям

Резюме

К указателям на динамические объекты не следует применять преобразование

static_cast
. Используйте безопасные альтернативы — от
dynamic_cast
до перепроектирования.

Обсуждение

Подумайте о замене

static_cast
более мощным оператором
dynamic_cast
, и вам не придется запоминать, в каких случаях применение
static_cast
безопасно, а в каких — чревато неприятностями. Хотя dynamic_cast может оказаться немного менее эффективным преобразованием, его применение позволяет обнаружить неверные преобразования типов (но не забывайте о рекомендации 8). Использование
static_cast
вместо
dynamic_cast
напоминает экономию на ночном освещении, когда выигрыш доллара в год оборачивается переломанными ногами.

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

Чрезмерное применение понижающего приведения может служить признаком слишком бедного интерфейса базового класса. Такой интерфейс может привести к тому, что большая функциональность определяется в производных классах, и всякий раз при необходимости расширения интерфейса приходится использовать понижающее приведение. Одно из решений данной проблемы — перепроектирование базового интерфейса в целях повышения функциональности.

Тогда и только тогда, когда становятся существенны накладные расходы, вызванные применением

dynamic_cast
(см. рекомендацию 8), следует подумать о разработке собственного преобразования типов, который использует
dynamic_cast
при отладке и
static_cast
в окончательной версии (см. [Stroustrup00]):

template<class To, class From> To checked_cast(From* from) {

 assert(dynamic_cast<To>(from) ==

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

 return static_cast<To>(from);

}

template<class To, class From> To checked_cast(From& from) {

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

 assert(dynamic_cast<ToPtr>(&from) ==

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

 return static_cast<To>(from);

}

Поделиться с друзьями: