Читать «Стандарты программирования на С++. 101 правило и рекомендация» онлайн - страница 142

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

 void Draw() const {

  switch (id_) { // плохой метод

  case RECTANGLE:

   // ... Код для прямоугольника …

   break;

  case TRIANGLE:

   // ... Код для треугольника …

   break;

  case CIRCLE:

   // ... Код для окружности …

   break;

  default: // Плохое решение

   assert(!"при добавлении нового типа надо "

           "обновить эту конструкцию" );

   break;

  }

 }

};

Такой код сгибается под собственным весом, он хрупок, ненадежен и сложен. В частности, он страдает транзитивной циклической зависимостью, о которой говорилось в рекомендации 22. Ветвь по умолчанию конструкции switch — четкий симптом синдрома "не знаю, что мне делать с этим типом". И все эти болезненные неприятности полностью исчезают, стоит только вспомнить, что С++ — объектно-ориентированный язык программирования:

class Shape { // ...

 virtual void Draw() const = 0; // Каждый производный

                                // класс реализует свою функцию

};

В качестве альтернативы (или в качестве дополнения) рассмотрим реализацию, которая следует совету по возможности принимать решения во время компиляции (см. рекомендацию 64):

template<class S>

void Draw(const S& shape) {

 shape.Draw(); // может быть виртуальной, а может и не быть

};             // См. рекомендацию 64

Теперь ответственность за рисование каждой геометрической фигуры переходит к реализации самой фигуры, и синдром "не знаю, что делать с этим типом" просто невозможен.

Ссылки

[Dewhurst03] §69, §96 • [Martin96c] • [Meyer00] • [Stroustrup00] §12.2.5 • [Sutter04] §36

91. Работайте с типами, а не с представлениями

Резюме

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

Обсуждение

Стандарт С++ дает очень мало гарантий по поводу представления типов в памяти.

• Целые числа используют двоичное представление.

• Для отрицательных чисел используется дополнительный код числа в двоичной системе.

• Обычные старые типы (Plain Old Data, POD) имеют совместимое с С размещение в памяти: переменные-члены хранятся в порядке их объявления.

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

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

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

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

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

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

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

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

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