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), использование автоматизированного инструментария для их отслеживания — неплохая идея.
Ссылки
[Dewhurst03] §40 • [Meyers96] §2 • [Stroustrup00] §15.4.5 • [Sutter00] §44
96. Не применяйте memcpy
или memcmp
к не-POD типам
Резюме
Не работайте рентгеновским аппаратом (см. рекомендацию 91). Не используйте memcpy
и memcmp
для копирования или сравнения чего-либо структурированного более, чем обычная память.
Обсуждение
Функции memcpy
и memcmp
нарушают систему типов. Использовать memcpy
для копирования объектов — это то же, что использовать ксерокс для копирования денег, а сравнивать объекты при помощи memcmp
— то же, что сравнивать двух леопардов по количеству пятен. Инструменты и методы могут казаться подходящими для выполнения работы, но они слишком грубы для того, чтобы сделать ее правильно.
Объекты С++ предназначены для сокрытия данных (возможно, наиболее важный принцип в разработке программного обеспечения; см. рекомендацию 11). Объекты скрывают данные (см. рекомендацию 41) и предоставляют точные абстракции для копирования этих данных посредством конструкторов и операторов присваивания (см. рекомендации с 52 по 55). Пройтись по ним грубым инструментом типа memcpy
— серьезное нарушение принципа сокрытия информации, которое зачастую приводит к утечкам памяти и ресурсов (в лучшем случае), аварийному завершению программы (в случае похуже) или неопределенному поведению (в самом худшем случае). Например:
{
// Создаем два int в памяти
shared_ptr p1(new int), p2(new int);
memcpy(&p1, &p2, sizeof(p1)); // Так делать нельзя!!!
} // Утечка памяти: p2 никогда не удаляется
// повреждение памяти: p1 удаляется дважды
Неверное применение memcpy
может влиять на такие фундаментальные свойства, как тип и сущность объекта. Компиляторы часто добавляют к полиморфным объектам скрытые данные (так называемый указатель на виртуальную таблицу), которые определяют сущность объекта во время выполнения программы. В случае множественного наследования в объекте содержится несколько таких таблиц, с различными смещениями внутри объекта, и большинство реализаций добавляют дополнительные внутренние указатели при виртуальном наследовании. При обычном использовании компилятор принимает меры для корректного управления всеми скрытыми полями; применение memcpy
способно внести в этот механизм только хаос.
Аналогично, функция memcmp
— неподходящий инструмент для сравнения чего-то более сложного, чем просто наборы битов. Иногда эта функция делает слишком мало (например, сравнение строк в стиле С — не то же, что и сравнение указателей, при помощи которых эти строки реализованы). А иногда, как это ни парадоксально, memcmp
делает слишком много (например, memcmp
может совершенно напрасно сравнивать байты, которые не являются частью состояния объекта, такие как заполнители, вставленные компилятором для выравнивания). В обоих случаях результат сравнения оказывается неверным.
Ссылки
[Dewhurst03] §50 • [Stroustrup94] §11.4.4
97. Не используйте объединения для преобразований
Резюме
Хитрость все равно остается ложью: объединения можно использовать для получения "преобразования типа без преобразования", записывая информацию в один член и считывая из другого. Однако это еще более опасно и менее предсказуемо, чем применение reinterpret_cast
(см. рекомендацию 92).
Читать дальше