Должен ли я использовать #define, enum или const? - PullRequest
121 голосов
/ 22 сентября 2008

В проекте C ++, над которым я работаю, у меня есть значение flag , которое может иметь четыре значения. Эти четыре флага могут быть объединены. Флаги описывают записи в базе данных и могут быть:

  • новая запись
  • удалённая запись
  • измененная запись
  • существующая запись

Теперь для каждой записи я хочу сохранить этот атрибут, чтобы я мог использовать перечисление:

enum { xNew, xDeleted, xModified, xExisting }

Однако в других местах кода мне нужно выбрать, какие записи должны быть видны пользователю, поэтому я хотел бы иметь возможность передавать это как один параметр, например:

showRecords(xNew | xDeleted);

Итак, у меня есть три возможных подхода:

#define X_NEW      0x01
#define X_DELETED  0x02
#define X_MODIFIED 0x04
#define X_EXISTING 0x08

или

typedef enum { xNew = 1, xDeleted, xModified = 4, xExisting = 8 } RecordType;

или

namespace RecordType {
    static const uint8 xNew = 1;
    static const uint8 xDeleted = 2;
    static const uint8 xModified = 4;
    static const uint8 xExisting = 8;
}

Требования к пространству важны (byte vs int), но не важны. С определениями я теряю безопасность типов, а с enum я теряю некоторое пространство (целые числа) и, вероятно, вынужден приводить, когда я хочу сделать битовую операцию. С const я думаю, что я также теряю безопасность типов, поскольку случайный uint8 мог попасть по ошибке.

Есть ли какой-нибудь другой более чистый способ?

Если нет, что бы вы использовали и почему?

P.S. Остальная часть кода - довольно чистый современный C ++ без #define s, и я использовал пространства имен и шаблоны в нескольких местах, так что о них тоже не может быть и речи.

Ответы [ 15 ]

2 голосов
/ 22 сентября 2008

На основании KISS , высокая когезия и низкая связь , задайте эти вопросы -

  • Кто должен знать? мой класс, моя библиотека, другие классы, другие библиотеки, третьи стороны
  • Какой уровень абстракции мне нужно предоставить? Понимает ли потребитель битовые операции.
  • Придется ли мне взаимодействовать с VB / C # и т. Д.?

Существует замечательная книга " Large-Scale C ++ Software Design ", которая продвигает базовые типы извне, если вы можете избежать другой зависимости заголовочного файла / интерфейса, которую вам следует попробовать.

2 голосов
/ 22 сентября 2008

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

Но какой бы метод вы ни использовали, чтобы было более понятно, что это значения, которые представляют собой биты, которые можно объединить вместе, используйте этот синтаксис для фактических значений:

#define X_NEW      (1 << 0)
#define X_DELETED  (1 << 1)
#define X_MODIFIED (1 << 2)
#define X_EXISTING (1 << 3)

Использование сдвига влево помогает указать, что каждое значение предназначено для одного бита, менее вероятно, что позже кто-то сделает что-то не так, как добавление нового значения и присвоение ему значения, равного 9.

2 голосов
/ 22 сентября 2008

Вам действительно нужно передавать значения флага как концептуальное целое, или у вас будет много кода для каждого флага? В любом случае, я думаю, что наличие этого класса или структуры 1-битных битовых полей может быть более понятным:

struct RecordFlag {
    unsigned isnew:1, isdeleted:1, ismodified:1, isexisting:1;
};

Тогда ваш класс записей может иметь переменную-член struct RecordFlag, функции могут принимать аргументы типа struct RecordFlag и т. Д. Компилятор должен упаковывать битовые поля вместе, экономя пространство.

0 голосов
/ 22 сентября 2008

Не то, чтобы мне нравилось все чрезмерно проектировать, но иногда в этих случаях, возможно, стоит создать (маленький) класс для инкапсуляции этой информации. Если вы создаете класс RecordType, то он может иметь такие функции, как:

void setDeleted ();

void clearDeleted ();

bool isDeleted ();

и т. Д. (Или в соответствии с соглашением)

Он может проверять комбинации (в случае, если не все комбинации являются допустимыми, например, если «новые» и «удаленные» не могут быть установлены одновременно). Если вы просто использовали битовые маски и т. Д., То код, который устанавливает состояние, должен проверяться, класс также может инкапсулировать эту логику.

Класс также может дать вам возможность прикреплять значимую информацию о журналировании к каждому состоянию, вы можете добавить функцию для возврата строкового представления текущего состояния и т. Д. (Или использовать потоковые операторы «<<»). </p>

Несмотря на это, если вы беспокоитесь о хранилище, у класса все равно может быть только элемент данных 'char', поэтому используйте только небольшой объем хранилища (при условии, что он не виртуальный). Конечно, в зависимости от оборудования и т. Д. У вас могут возникнуть проблемы с выравниванием.

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

Если вы обнаружите, что в коде, использующем enum / # define / bitmask и т. Д., Имеется много кода «поддержки» для работы с недопустимыми комбинациями, ведением журнала и т. Д., Возможно, стоит рассмотреть инкапсуляцию в классе. Конечно, в большинстве случаев простые проблемы лучше с простыми решениями ...

0 голосов
/ 22 сентября 2008

Я бы лучше пошел с

typedef enum { xNew = 1, xDeleted, xModified = 4, xExisting = 8 } RecordType;

Просто потому что:

  1. Он чище и делает код читабельным и понятным.
  2. Логически группирует константы.
  3. Время программиста важнее, если ваша задача не для сохранения этих 3 байтов
...