Что мне следует использовать #define, enum или const?

В проекте 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

or

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

or

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 ++ без #defines, и я использовал пространства имен и шаблоны в нескольких местах, так что это тоже не исключено.


person Milan Babuškov    schedule 21.09.2008    source источник
comment
с enum я теряю некоторое пространство (целые числа). Не обязательно. См. stackoverflow.com/questions/366017/ и stackoverflow.com/questions/1113855/-fshort- enum (я предполагаю, что эти ответы на C по-прежнему верны для C ++.)   -  person idbrii    schedule 24.05.2012
comment
@pydave Если вы не уверены в совместимости C и C ++, я считаю эту ссылку очень полезной, см., например, enum david.tribble.com/text/cdiffs.htm#C99-enum-type   -  person aka.nice    schedule 09.08.2012
comment
Это более старая тема с большим количеством голосов, есть ли причина не упоминать классы перечисления C ++ 11 для этой проблемной ситуации.   -  person Brandin    schedule 31.08.2014
comment
Отметим, что enum RecordType : uint8_t сочетает в себе безопасность типов enum с небольшим размером uint8_t, хотя вам все равно придется предоставлять побитовые операторы.   -  person Justin Time - Reinstate Monica    schedule 01.10.2019


Ответы (15)


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

Поместите перечисление в пространство имен, чтобы константы не загрязняли глобальное пространство имен.

namespace RecordType {

Перечисление объявляет и определяет типизированное время компиляции с проверкой. Всегда используйте проверку типа во время компиляции, чтобы убедиться, что аргументы и переменные имеют правильный тип. В C ++ нет необходимости в typedef.

enum TRecordType { xNew = 1, xDeleted = 2, xModified = 4, xExisting = 8,

Создайте еще один член для недопустимого состояния. Это может быть полезно как код ошибки; например, если вы хотите вернуть состояние, но операция ввода-вывода не выполняется. Это также полезно для отладки; используйте его в списках инициализации и деструкторах, чтобы узнать, следует ли использовать значение переменной.

xInvalid = 16 };

Учтите, что у вас есть две цели для этого типа. Для отслеживания текущего состояния записи и создания маски для выбора записей в определенных состояниях. Создайте встроенную функцию, чтобы проверить, подходит ли значение типа для ваших целей; как маркер состояния по сравнению с маской состояния. Это позволит отловить ошибки, поскольку typedef - это просто int, а значение, такое как 0xDEADBEEF, может быть в вашей переменной через неинициализированные или неверно указанные переменные.

inline bool IsValidState( TRecordType v) {
    switch(v) { case xNew: case xDeleted: case xModified: case xExisting: return true; }
    return false;
}

 inline bool IsValidMask( TRecordType v) {
    return v >= xNew  && v < xInvalid ;
}

Добавьте директиву using, если хотите часто использовать этот тип.

using RecordType ::TRecordType ;

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

Вот несколько примеров, чтобы собрать все это воедино.

void showRecords(TRecordType mask) {
    assert(RecordType::IsValidMask(mask));
    // do stuff;
}

void wombleRecord(TRecord rec, TRecordType state) {
    assert(RecordType::IsValidState(state));
    if (RecordType ::xNew) {
    // ...
} in runtime

TRecordType updateRecord(TRecord rec, TRecordType newstate) {
    assert(RecordType::IsValidState(newstate));
    //...
    if (! access_was_successful) return RecordType ::xInvalid;
    return newstate;
}

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

person mat_geek    schedule 22.09.2008
comment
В основном хороший ответ, но в вопросе указано, что флаги можно комбинировать, а функция IsValidState () не позволяет их комбинировать. - person Jonathan Leffler; 01.12.2008
comment
@Jonathan Leffler: с моей точки зрения, я думаю, что IsValidState не должен этого делать, IsValidMask. - person João Portela; 11.01.2010
comment
Желательно, чтобы IsValidMask не позволял выбрать ничего (т.е. 0)? - person Joachim Sauer; 10.08.2011
comment
−1 Идея проверки типов во время выполнения - мерзость. - person Cheers and hth. - Alf; 31.08.2016

Забудьте об определениях

Они загрязнят ваш код.

битовые поля?

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

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

However, bit members in structs have practical drawbacks. First, the ordering of bits in memory varies from compiler to compiler. In addition, many popular compilers generate inefficient code for reading and writing bit members, and there are potentially severe thread safety issues relating to bit fields (especially on multiprocessor systems) due to the fact that most machines cannot manipulate arbitrary sets of bits in memory, but must instead load and store whole words. e.g the following would not be thread-safe, in spite of the use of a mutex

Источник: http://en.wikipedia.org/wiki/Bit_field:

И если вам нужно больше причин, чтобы не использовать битовые поля, возможно, Раймонд Чен убедит вас в своем сообщении The Old New Thing: Анализ рентабельности битовых полей для коллекция логических значений на http://blogs.msdn.com/oldnewthing/archive/2008/11/26/9143050.aspx.

const int?

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

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

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

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

Проблема этого подхода в том, что ваш код знает значение ваших констант, что немного увеличивает связь.

перечислить

То же, что и const int, с несколько более строгим типированием.

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

Однако они все еще загрязняют глобальное пространство имен. Кстати ... Удалите typedef. Вы работаете на C ++. Эти определения типов перечислений и структур загрязняют код больше, чем что-либо другое.

Результат вроде бы:

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

void doSomething(RecordType p_eMyEnum)
{
   if(p_eMyEnum == xNew)
   {
       // etc.
   }
}

Как видите, ваше перечисление загрязняет глобальное пространство имен. Если вы поместите это перечисление в пространство имен, у вас будет что-то вроде:

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

void doSomething(RecordType::Value p_eMyEnum)
{
   if(p_eMyEnum == RecordType::xNew)
   {
       // etc.
   }
}

extern const int?

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

// Header.hpp
namespace RecordType {
    extern const uint8 xNew ;
    extern const uint8 xDeleted ;
    extern const uint8 xModified ;
    extern const uint8 xExisting ;
}

И:

// Source.hpp
namespace RecordType {
    const uint8 xNew = 1;
    const uint8 xDeleted = 2;
    const uint8 xModified = 4;
    const uint8 xExisting = 8;
}

Однако вы не сможете использовать switch для этих констант. Так что, в конце концов, выберите свой яд ... :-p

person paercebal    schedule 21.09.2008
comment
Как вы думаете, почему битовые поля медленные? Вы действительно профилировали код, используя его и другой метод? Даже если это так, ясность может быть важнее скорости, поэтому никогда не используйте это немного упрощенным. - person wnoise; 22.09.2008
comment
static const uint8 xNew; является избыточным только потому, что в C ++ переменные с ограниченным пространством имен const по умолчанию имеют внутреннюю связь. Удалите const, и у него будет внешняя связь. Кроме того, enum {...} RecordType; объявляет глобальную переменную с именем RecordType, тип которой является анонимным перечислением. - person bk1e; 22.09.2008
comment
onebyone: Во-первых, основная причина заключалась в том, что выигрыш (несколько байтов, если они есть) был перекрыт потерями (медленнее доступ, как для чтения, так и для записи) ... - person paercebal; 24.09.2008
comment
onebyone: Во-вторых, весь код, который я создаю на работе или дома, по своей сути является потокобезопасным. Это легко сделать: без глобальных переменных, статических переменных, не разделяемых между потоками, если не защищено блокировкой. Использование этой идиомы нарушит эту базовую потокобезопасность. А для чего? Несколько байтов возможно? ... :-) ... - person paercebal; 24.09.2008
comment
Добавлена ​​ссылка на статью Раймонда Чена о скрытых затратах битовых полей. - person paercebal; 01.12.2008

Вы исключили std :: bitset? Наборы флагов - вот для чего это нужно. Делать

typedef std::bitset<4> RecordType;

тогда

static const RecordType xNew(1);
static const RecordType xDeleted(2);
static const RecordType xModified(4);
static const RecordType xExisting(8);

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

RecordType rt = whatever;      // unsigned long or RecordType expression
rt |= xNew;                    // set 
rt &= ~xDeleted;               // clear 
if ((rt & xModified) != 0) ... // test

Или что-то очень похожее на это - я был бы признателен за любые исправления, так как я это не тестировал. Вы также можете ссылаться на биты по индексу, но обычно лучше определять только один набор констант, и константы RecordType, вероятно, более полезны.

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

Я не верю, что приведение перечислений является серьезным недостатком - хорошо, так что это немного шумно, а присвоение значения, выходящего за пределы диапазона, является неопределенным поведением, поэтому теоретически можно выстрелить себе в ногу на каком-то необычном C ++ реализации. Но если вы делаете это только при необходимости (то есть при переходе от int к enum iirc), это совершенно нормальный код, который люди видели раньше.

Я тоже сомневаюсь в том, что перечисление занимает какое-либо место. Переменные и параметры uint8, вероятно, не будут использовать меньше стека, чем ints, поэтому имеет значение только хранение в классах. Бывают случаи, когда упаковка нескольких байтов в структуру дает преимущество (и в этом случае вы можете приводить перечисления в хранилище uint8 и из него), но обычно заполнение все равно убивает выгоду.

Таким образом, перечисление не имеет недостатков по сравнению с другими, и как преимущество дает вам некоторую безопасность типов (вы не можете назначить какое-то случайное целочисленное значение без явного приведения) и чистые способы ссылки на все.

Кстати, для предпочтения я бы также поставил "= 2" в перечисление. В этом нет необходимости, но «принцип наименьшего удивления» предполагает, что все 4 определения должны выглядеть одинаково.

person Steve Jessop    schedule 21.09.2008
comment
Собственно, битсет я вообще не рассматривал. Однако я не уверен, что это было бы хорошо. С битовым набором я должен адресовать биты как 1, 2, 3, 4, что сделало бы код менее читаемым - это означает, что я, вероятно, использовал бы перечисление для «именования» битов. Хотя мог бы сэкономить место. Спасибо. - person Milan Babuškov; 22.09.2008
comment
Милан, вам не нужно называть биты с помощью перечисления, вы можете просто использовать предопределенные биты, как показано выше. Если вы хотите включить бит один, а не my_bitset.flip (1), вы должны сделать my_bitset | = xNew; - person moswald; 23.09.2008
comment
это меньше направлено на вас и больше на STL, но: я действительно должен спросить: зачем вам использовать bitset для этого? он обычно переводится в long (в моей реализации iirc; да, насколько расточительно) или аналогичный интегральный тип для каждого элемента в любом случае, так почему бы просто не использовать не запутанные интегралы? (или, в настоящее время, constexpr с нулевым хранилищем) - person underscore_d; 20.04.2016
comment
[править таймаут] ... но тогда я никогда толком не понимал смысла использования класса bitset, за исключением того, что кажется повторяющимся скрытым течением в окружающих обсуждениях: «тьфу, мы должны скрыть неприятные низкоуровневые корни язык' - person underscore_d; 20.04.2016
comment
uint8 переменные и параметры, вероятно, не будут использовать стек меньше, чем ints неверно. Если у вас есть ЦП с 8-битными регистрами, int нужно как минимум 2 регистра, а uint8_t - только 1, поэтому вам понадобится больше места в стеке, потому что у вас больше шансов не получить регистры (что также медленнее и может увеличить код размер (в зависимости от набора инструкций)). (У вас есть тип, он должен быть uint8_t, а не uint8) - person 12431234123412341234123; 25.08.2017

Вот пара статей о константах, макросах и перечислениях:

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

Я думаю, вам следует избегать макросов, тем более что вы написали большую часть своего нового кода на современном C ++.

person Abbas    schedule 21.09.2008

По возможности НЕ используйте макросы. Когда дело доходит до современного C ++, ими не слишком восхищаются.

person INS    schedule 22.09.2008
comment
Истинный. Что я ненавижу в макросах, так это то, что в них нельзя вмешиваться, если они ошибочны. - person Carl; 01.12.2008
comment
Думаю, это можно исправить в компиляторе. - person celticminstrel; 05.07.2015

Перечисления были бы более подходящими, поскольку они обеспечивают «значение идентификаторов», а также безопасность типов. Вы можете ясно сказать, что «xDeleted» относится к «RecordType» и представляет собой «тип записи» (вау!) Даже спустя годы. Для этого нужны комментарии, а также переходы вверх и вниз в коде.

person Community    schedule 21.09.2008

С определениями я теряю безопасность типа

Не обязательно...

// signed defines
#define X_NEW      0x01u
#define X_NEW      (unsigned(0x01))  // if you find this more readable...

и с enum я теряю немного места (целые числа)

Не обязательно - но вы должны быть точными в точках хранения ...

struct X
{
    RecordType recordType : 4;  // use exactly 4 bits...
    RecordType recordType2 : 4;  // use another 4 bits, typically in the same byte
    // of course, the overall record size may still be padded...
};

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

Вы можете создавать операторы, чтобы избавиться от этого:

RecordType operator|(RecordType lhs, RecordType rhs)
{
    return RecordType((unsigned)lhs | (unsigned)rhs);
}

Я думаю, что с const я также теряю безопасность типов, поскольку случайный uint8 может попасть по ошибке.

То же самое может произойти с любым из этих механизмов: проверки диапазонов и значений обычно ортогональны типобезопасности (хотя пользовательские типы, то есть ваши собственные классы, могут обеспечивать "инварианты" своих данных). С перечислениями компилятор может выбрать более крупный тип для размещения значений, а неинициализированная, поврежденная или просто неверно установленная переменная перечисления может по-прежнему интерпретировать свой битовый шаблон как число, которого вы не ожидаете - сравнение неравных с любым из идентификаторы перечисления, любая их комбинация и 0.

Есть ли другой способ чище? / Если нет, что бы вы использовали и почему?

Что ж, в конце концов, испытанное и надежное побитовое ИЛИ перечислений в стиле C работает очень хорошо, если у вас есть битовые поля и пользовательские операторы на картинке. Вы можете дополнительно улучшить свою надежность с помощью некоторых пользовательских функций проверки и утверждений, как в ответе mat_geek; методы, которые в равной степени применимы к обработке строковых, целочисленных, двойных значений и т. д.

Вы можете возразить, что это «чище»:

enum RecordType { New, Deleted, Modified, Existing };

showRecords([](RecordType r) { return r == New || r == Deleted; });

Мне безразлично: биты данных упаковываются плотнее, но код значительно увеличивается ... зависит от того, сколько у вас объектов, а lamdbas - какими бы красивыми они ни были - все еще более беспорядочные и трудные для правильного выполнения, чем побитовые ИЛИ.

BTW / - аргумент о довольно слабой безопасности потоков IMHO - лучше всего помнить как фоновое соображение, а не стать доминирующей движущей силой решения; совместное использование мьютексов между битовыми полями является более вероятной практикой, даже если не знать об их упаковке (мьютексы являются относительно громоздкими элементами данных - мне нужно действительно беспокоиться о производительности, чтобы рассмотреть возможность наличия нескольких мьютексов на элементах одного объекта, и я бы внимательно посмотрел достаточно, чтобы заметить, что это были битовые поля). Такая же проблема может возникнуть у любого типа размером с вложенное слово (например, uint8_t). В любом случае, вы можете попробовать операции в стиле атомарного сравнения и замены, если вам отчаянно нужен более высокий уровень параллелизма.

person Tony Delroy    schedule 07.08.2012
comment
+1 Отлично. Но operator| должен быть приведен к целочисленному типу (unsigned int) перед инструкцией |. В противном случае operator| будет рекурсивно вызывать себя и вызывать переполнение стека во время выполнения. Предлагаю: return RecordType( unsigned(lhs) | unsigned(rhs) );. Ваше здоровье - person oHo; 11.01.2013

Даже если вам нужно использовать 4 байта для хранения перечисления (я не так хорошо знаком с C ++ - я знаю, что вы можете указать базовый тип в C #), это все равно того стоит - используйте перечисления.

В наше время серверов с гигабайтами памяти такие вещи, как 4 байта против 1 байта памяти на уровне приложения в целом не имеют значения. Конечно, если в вашей конкретной ситуации использование памяти так важно (и вы не можете заставить C ++ использовать байт для поддержки перечисления), вы можете рассмотреть маршрут 'static const'.

В конце концов, вы должны спросить себя, стоит ли использовать «статическую константу» для экономии 3 байтов памяти для вашей структуры данных?

Следует иметь в виду еще кое-что - IIRC, на x86, структуры данных выровнены по 4 байта, поэтому, если у вас нет нескольких элементов ширины байта в структуре «записи», это может не иметь значения. Протестируйте и убедитесь, что это так, прежде чем идти на компромисс между ремонтопригодностью и производительностью / пространством.

person Jonathan Rupp    schedule 21.09.2008
comment
Вы можете указать базовый тип в C ++, начиная с версии языка C ++ 11. До этого я полагал, что оно было, по крайней мере, достаточно большим для хранения и использования в качестве битового поля для всех указанных перечислителей, но, вероятно, int, если оно не слишком мало. [Если вы не укажете базовый тип в C ++ 11, он будет использовать устаревшее поведение. И наоборот, базовый тип C ++ 11 enum class по умолчанию явно принимает значение int, если не указано иное.] - person Justin Time - Reinstate Monica; 02.10.2019

Если вам нужна безопасность типов классов с удобством синтаксиса перечисления и проверки битов, рассмотрите возможность Safe Labels в C ++. Я работал с автором, он довольно умен.

Однако будьте осторожны. В конце концов, в этом пакете используются шаблоны и макросы!

person Don Wakefield    schedule 22.09.2008
comment
Похоже, мое маленькое приложение переборщило. но это кажется хорошим решением. - person Milan Babuškov; 22.09.2008

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

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

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

person wnoise    schedule 21.09.2008
comment
Иногда целиком, иногда в виде флага. И мне также нужно проверить, установлен ли определенный флаг (когда я передаю его целиком). - person Milan Babuškov; 22.09.2008
comment
ну, когда отдельно, просто попросите int. Когда вместе, передайте struct. - person wnoise; 22.09.2008
comment
Лучше не будет. Доступ к битовым полям происходит медленнее, чем что-либо еще. - person paercebal; 22.09.2008
comment
Действительно? Вы думаете, что компилятор сгенерирует код для тестирования битовых полей, существенно отличный от ручного? И что будет значительно медленнее? Почему? Единственное, что вы не можете сделать так легко идиоматически, - это замаскировать сразу несколько флагов. - person wnoise; 22.09.2008
comment
Запустив простой тест чтения, я получаю 5,50–5,58 секунды для битовой маскировки против 5,45–5,59 для доступа к битовому полю. Практически неотличимо. - person wnoise; 22.09.2008
comment
Не используйте битовые поля - они почти всегда доставляют больше хлопот, чем они того стоят. - person Jonathan Leffler; 01.12.2008
comment
См. Статью о Чене, на которую ссылается @paercebal. - person Jonathan Leffler; 01.12.2008

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

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

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

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

person Community    schedule 22.09.2008
comment
Для этого есть достаточно прецедентов, особенно в константах для ioctl (). Я предпочитаю использовать шестнадцатеричные константы: 0x01, 0x02, 0x04, 0x08, 0x10, ... - person Jonathan Leffler; 01.12.2008

На основе KISS, высокая согласованность и низкая взаимосвязь, задайте следующие вопросы -

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

Есть отличная книга "Разработка крупномасштабного программного обеспечения на C ++ ", это продвигает базовые типы извне, если вы можете избежать другой зависимости файла заголовка / интерфейса, которую вы должны попробовать.

person titanae    schedule 22.09.2008
comment
а) 5-6 классы. б) только я, это проект одного человека в) без интерфейса - person Milan Babuškov; 22.09.2008

Если вы используете Qt, вам следует поискать QFlags. Класс QFlags обеспечивает безопасный для типов способ хранения ИЛИ-комбинаций значений перечисления.

person Thomas Koschel    schedule 22.09.2008
comment
Нет, нет Qt. Собственно, это проект wxWidgets. - person Milan Babuškov; 22.09.2008

Я бы предпочел пойти с

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

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

  1. Он чище, делает код читабельным и поддерживаемым.
  2. Он логически группирует константы.
  3. Время программиста более важно, если только ваша задача не сохранить эти 3 байта.
person Vivek    schedule 21.09.2008
comment
Ну, у меня легко мог бы быть миллион экземпляров класса Record, так что это может быть важно. OTOH, это просто разница между 1 МБ и 4 МБ, так что, может, мне не о чем беспокоиться. - person Milan Babuškov; 22.09.2008
comment
@Vivek: Вы учли ограничение ширины целого числа? В частности, до C ++ 11. - person user2672165; 13.08.2013

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

void setDeleted ();

void clearDeleted ();

bool isDeleted ();

и т. д. (или что угодно по соглашению)

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

Класс также может дать вам возможность прикреплять значимую информацию о журнале к каждому состоянию, вы можете добавить функцию для возврата строкового представления текущего состояния и т. Д. (Или использовать операторы потоковой передачи '‹*').

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

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

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

person Community    schedule 22.09.2008
comment
К сожалению, объявление должно быть в файле .h, поскольку оно используется во всем проекте (используется некоторыми 5-6 классами). - person Milan Babuškov; 22.09.2008