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

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

  • новый рекорд
  • удаленная запись
  • измененная запись
  • существующая запись

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

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

«с enum я теряю некоторое пространство (целые числа)». Не обязательно. См. stackoverflow.com/questions/366017/… и stackoverflow.com/questions/1113855/…-fshort-enum от gcc. (Я предполагаю, что эти ответы C все еще верны для C++).)

idbrii 24.05.2012 19:44

@pydave Если вы не уверены в совместимости C и C++, я считаю эту ссылку очень полезной, см., например, enum david.tribble.com/text/cdiffs.htm#C99-enum-type

aka.nice 09.08.2012 01:23

Это более старая тема с большим количеством голосов, есть ли причина не упоминать классы перечисления C++ 11 для этой проблемной ситуации.

Brandin 31.08.2014 17:17

Отметим, что enum RecordType : uint8_t сочетает в себе безопасность типов enum с небольшим размером uint8_t, хотя вам все равно придется предоставлять побитовые операторы.

Justin Time - Reinstate Monica 01.10.2019 21:22
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
126
4
65 043
15
Перейти к ответу Данный вопрос помечен как решенный

Ответы 15

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

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

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

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

Вы можете указать базовый тип в C++, начиная с версии языка C++ 11. До этого я полагал, что он был «по крайней мере достаточно большим для хранения и использования в качестве битового поля для всех указанных перечислителей, но, вероятно, int, если он не слишком мал». [Если вы не укажете базовый тип в C++ 11, он будет использовать устаревшее поведение. И наоборот, базовый тип C++ 11 enum class по умолчанию явно принимает значение int, если не указано иное.]

Justin Time - Reinstate Monica 02.10.2019 20:46

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

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

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

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

Вы исключили 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 определения должны выглядеть одинаково.

Собственно, битсет я вообще не рассматривал. Однако я не уверен, что это было бы хорошо. С битовым набором я должен адресовать биты как 1, 2, 3, 4, что сделало бы код менее читаемым - это означает, что я, вероятно, использовал бы перечисление для «именования» битов. Хотя мог бы сэкономить место. Спасибо.

Milan Babuškov 22.09.2008 03:12

Милан, вам не нужно «называть» биты с помощью перечисления, вы можете просто использовать предопределенные биты, как показано выше. Если вы хотите включить бит один, а не my_bitset.flip (1), вы должны сделать my_bitset | = xNew;

moswald 23.09.2008 00:37

это меньше направлено на вас и больше на STL, но: я действительно должен спросить: зачем вам использовать bitset для этого? обычно он переводится в long (в моей реализации iirc; да, насколько расточительно) или аналогичный интегральный тип для каждого элемента в любом случае, так почему бы просто не использовать не запутанные интегралы? (или, в настоящее время, constexpr с нулевым хранилищем)

underscore_d 20.04.2016 22:46

[править тайм-аут] ... но тогда я никогда толком не понимал смысла класса bitset, за исключением того, что кажется повторяющимся скрытым течением в окружающих обсуждениях «тьфу, мы должны скрыть неприятные низкоуровневые корни язык'

underscore_d 20.04.2016 22:53

«Переменные и параметры uint8, вероятно, не будут использовать стек меньше, чем ints» неверно. Если у вас есть ЦП с 8-битными регистрами, int потребуется как минимум 2 регистра, а uint8_t - только 1, поэтому вам понадобится больше места в стеке, потому что у вас больше шансов, что у вас не будет регистров (что также медленнее и может увеличить код размер (в зависимости от набора инструкций)). (У вас есть тип, он должен быть uint8_t, а не uint8)

12431234123412341234123 25.08.2017 12:32

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

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

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

Иногда целиком, иногда в виде флага. И мне также нужно проверить, установлен ли определенный флаг (когда я передаю его целиком).

Milan Babuškov 22.09.2008 03:19

ну, когда отдельно, просто попросите int. Когда вместе, передайте struct.

wnoise 22.09.2008 03:27

Лучше не будет. Доступ к битовым полям медленнее, чем что-либо еще.

paercebal 22.09.2008 03:36

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

wnoise 22.09.2008 03:43

Запустив простой тест чтения, я получаю 5,50–5,58 секунды для битовой маскировки против 5,45–5,59 для доступа к битовому полю. Практически неотличимо.

wnoise 22.09.2008 04:34

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

Jonathan Leffler 01.12.2008 00:29

См. Статью о Чене, на которую ссылается @paercebal.

Jonathan Leffler 01.12.2008 00:47

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

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

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

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

Ну, у меня легко мог бы быть миллион экземпляров класса Record, так что это может быть важно. OTOH, это просто разница между 1 МБ и 4 МБ, так что, может, мне не о чем беспокоиться.

Milan Babuškov 22.09.2008 03:35

@Vivek: Вы учли ограничение ширины целого числа? В частности, до C++ 11.

user2672165 13.08.2013 15:59

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

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

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

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:

И если вам нужно больше причин, чтобы нет использовал битовые поля, возможно, Раймонд Чен убедит вас в своем сообщении Старая новая вещь: Анализ рентабельности битовых полей для набора логических значений at 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 для этих констант. Итак, в конце концов, выберите свой яд ... :-п

Как вы думаете, почему битовые поля медленные? Вы действительно профилировали код, используя его и другой метод? Даже если это так, ясность может быть важнее скорости, что немного упрощает «никогда не используйте это».

wnoise 22.09.2008 04:11

"static const uint8 xNew;" является избыточным только потому, что в C++ переменные с ограниченным пространством имен const по умолчанию имеют внутреннюю связь. Удалите «const», и у него будет внешняя связь. Кроме того, "enum {...} RecordType;" объявляет глобальную переменную с именем «RecordType», тип которой является анонимным перечислением.

bk1e 22.09.2008 05:28

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

paercebal 24.09.2008 21:05

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

paercebal 24.09.2008 21:07

Добавлена ​​ссылка на статью Раймонда Чена о скрытых затратах битовых полей.

paercebal 01.12.2008 00:02

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

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

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

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

Для этого есть достаточно прецедентов, особенно в константах для ioctl (). Я предпочитаю использовать шестнадцатеричные константы: 0x01, 0x02, 0x04, 0x08, 0x10, ...

Jonathan Leffler 01.12.2008 00:42

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

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

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

Milan Babuškov 22.09.2008 12:15

Основываясь на ЦЕЛОВАТЬ, высокая когезия и низкая связь, задайте эти вопросы -

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

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

а) 5-6 классы. б) только я, это проект одного человека в) без интерфейса

Milan Babuškov 22.09.2008 12:16

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

Нет, нет Qt. Собственно, это проект wxWidgets.

Milan Babuškov 22.09.2008 12:09

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

Истинный. Что я ненавижу в макросах, так это то, что в них нельзя вмешиваться, если они ошибочны.

Carl 01.12.2008 00:33

Думаю, это можно исправить в компиляторе.

celticminstrel 05.07.2015 18:47
Ответ принят как подходящий

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

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

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;
}

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

В основном хороший ответ, но в вопросе указано, что флаги можно комбинировать, а функция IsValidState () не позволяет их комбинировать.

Jonathan Leffler 01.12.2008 00:46

@Jonathan Leffler: с моей точки зрения, я думаю, что IsValidState не должен этого делать, IsValidMask.

João Portela 11.01.2010 21:07

Желательно, чтобы IsValidMask не позволял выбрать ничего (например, 0)?

Joachim Sauer 10.08.2011 14:56
−1 The idea of runtime type checking is an abomination.
Cheers and hth. - Alf 31.08.2016 03:54

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

void setDeleted ();

void clearDeleted ();

bool isDeleted ();

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

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

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

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

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

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

К сожалению, объявление должно быть в файле .h, поскольку оно используется во всем проекте (используется некоторыми 5-6 классами).

Milan Babuškov 22.09.2008 17:23

With defines I lose type safety

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

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

and with enum I lose some space (integers)

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

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...
};

and probably have to cast when I want to do bitwise operation.

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

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

With const I think I also lose type safety since a random uint8 could get in by mistake.

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

Is there some other cleaner way? / If not, what would you use and why?

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

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

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

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

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

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

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

oHo 11.01.2013 14:27

Другие вопросы по теме