В проекте 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
могли по ошибке.
Есть ли другой способ чище?
Если нет, что бы вы использовали и почему?
PS Остальной код - довольно чистый современный C ++ без #define
s, и я использовал пространства имен и шаблоны в нескольких местах, так что это тоже не исключено.
источник
enum RecordType : uint8_t
сочетает в себе безопасность типаenum
с небольшим размеромuint8_t
, хотя вам все равно придется предоставлять побитовые операторы.Ответы:
Комбинируйте стратегии, чтобы уменьшить недостатки одного подхода. Я работаю во встроенных системах, поэтому следующее решение основано на том факте, что целочисленные и побитовые операторы работают быстро, занимают мало памяти и мало используют флэш-память.
Поместите перечисление в пространство имен, чтобы константы не загрязняли глобальное пространство имен.
Перечисление объявляет и определяет время компиляции, проверенное типизированным. Всегда используйте проверку типа во время компиляции, чтобы убедиться, что аргументы и переменные имеют правильный тип. В C ++ нет необходимости в typedef.
Создайте еще один член для недопустимого состояния. Это может быть полезно как код ошибки; например, если вы хотите вернуть состояние, но операция ввода-вывода не выполняется. Это также полезно для отладки; используйте его в списках инициализации и деструкторах, чтобы узнать, следует ли использовать значение переменной.
Учтите, что у вас есть две цели для этого типа. Для отслеживания текущего состояния записи и создания маски для выбора записей в определенных состояниях. Создайте встроенную функцию, чтобы проверить, подходит ли значение типа для ваших целей; как маркер состояния по сравнению с маской состояния. Это позволит отловить ошибки, поскольку
typedef
это простоint
и значение, такое как0xDEADBEEF
может быть в вашей переменной через неинициализированные или неверно указанные переменные.Добавьте
using
директиву, если хотите часто использовать тип.Функции проверки значений полезны в утверждениях для перехвата неверных значений сразу после их использования. Чем быстрее вы поймаете жук во время бега, тем меньше ущерба он может нанести.
Вот несколько примеров, чтобы собрать все это вместе.
Единственный способ обеспечить безопасность правильных значений - использовать выделенный класс с перегрузками операторов, и это оставлено в качестве упражнения для другого читателя.
источник
IsValidMask
не было возможности выбрать ничего (т.е.0
)?Забудьте об определениях
Они будут загрязнять ваш код.
битовые?
Никогда не используйте это . Вас больше заботит скорость, чем экономия 4-х единиц. Использование битовых полей на самом деле медленнее, чем доступ к любому другому типу.
Источник: http://en.wikipedia.org/wiki/Bit_field :
И если вам нужно больше причин не использовать битовые поля, возможно, Рэймонд Чен убедит вас в своей статье The Old New Thing Post: Анализ затрат и выгод битовых полей для набора логических значений на http://blogs.msdn.com/oldnewthing/ Архив / 2008/11/26 / 9143050.aspx
const int?
Помещать их в пространство имен - это круто. Если они объявлены в вашем CPP или файле заголовка, их значения будут встроены. Вы сможете использовать переключатель для этих значений, но это немного увеличит связь.
Ах, да: удалите ключевое слово static . static не рекомендуется в C ++ при использовании в том же порядке, и если uint8 является типом сборки, вам не нужно это объявлять в заголовке, включенном несколькими источниками одного и того же модуля. В итоге код должен быть:
Проблема этого подхода в том, что ваш код знает значение ваших констант, что немного увеличивает связь.
перечисление
То же, что и const int, с несколько более строгим типированием.
Однако они все еще загрязняют глобальное пространство имен. Кстати ... Удалите typedef . Вы работаете на C ++. Эти определения перечислений и структур загрязняют код больше, чем что-либо другое.
Результат вроде бы:
Как видите, ваше перечисление загрязняет глобальное пространство имен. Если вы поместите это перечисление в пространство имен, у вас будет что-то вроде:
extern const int?
Если вы хотите уменьшить связь (то есть иметь возможность скрыть значения констант и, таким образом, изменять их по желанию, не требуя полной перекомпиляции), вы можете объявить целые числа как extern в заголовке и как константы в файле CPP. , как в следующем примере:
И:
Однако вы не сможете использовать switch для этих констант. Итак, в конце концов, выберите свой яд ... :-p
источник
Вы исключили std :: bitset? Наборы флагов - вот для чего это нужно. Делать
затем
Поскольку для битового набора существует множество перегрузок операторов, теперь вы можете сделать
Или что-то очень похожее на это - я был бы признателен за любые исправления, так как я это не тестировал. Вы также можете ссылаться на биты по индексу, но обычно лучше определять только один набор констант, и константы RecordType, вероятно, более полезны.
Предполагая, что вы исключили битовый набор, я голосую за перечисление .
Я не верю, что приведение перечислений является серьезным недостатком - хорошо, так что это немного шумно, а присвоение значения, выходящего за пределы диапазона, является неопределенным поведением, поэтому теоретически можно выстрелить себе в ногу на каком-то необычном C ++ реализации. Но если вы делаете это только при необходимости (то есть при переходе от int к enum iirc), это совершенно нормальный код, который люди видели раньше.
Я тоже сомневаюсь в стоимости места в перечислении. Переменные и параметры uint8, вероятно, не будут использовать меньше стека, чем ints, поэтому имеет значение только хранение в классах. Бывают случаи, когда упаковка нескольких байтов в структуру дает преимущество (в этом случае вы можете приводить перечисления в хранилище uint8 и из него), но обычно заполнение все равно убивает выгоду.
Таким образом, перечисление не имеет недостатков по сравнению с другими, и в качестве преимущества дает вам некоторую безопасность типов (вы не можете назначить какое-то случайное целочисленное значение без явного приведения) и чистые способы ссылки на все.
Кстати, для предпочтения я бы также поместил в перечисление "= 2". В этом нет необходимости, но «принцип наименьшего удивления» предполагает, что все 4 определения должны выглядеть одинаково.
источник
bitset
? он обычно переводится вlong
(в моей реализации iirc; да, насколько расточительно) или аналогичный интегральный тип для каждого элемента в любом случае, так почему бы просто не использовать не запутанные интегралы? (или, в настоящее время,constexpr
с нулевым хранилищем)bitset
класса, за исключением того, что кажется повторяющимся скрытым течением в окружающих обсуждениях «тьфу, мы должны скрыть неприятные низкоуровневые корни языка» 'uint8
переменные и параметры, вероятно, не будут использовать стек меньше, чемints
» неверно. Если у вас есть ЦП с 8-битными регистрами, вамint
потребуется как минимум 2 регистра, тогда какuint8_t
нужен только 1, поэтому вам понадобится больше места в стеке, потому что у вас больше шансов быть вне регистров (что также медленнее и может увеличить размер кода ( в зависимости от комплекта инструкций)). ( У вас есть тип, он должен бытьuint8_t
неuint8
)Вот пара статей о константах, макросах и перечислениях:
Символьные константы
Перечисление констант и константные объекты
Я думаю, вам следует избегать макросов, тем более что вы написали большую часть своего нового кода на современном C ++.
источник
По возможности НЕ используйте макросы. Когда дело доходит до современного C ++, ими не слишком восхищаются.
источник
Перечисления были бы более подходящими, поскольку они предоставляют «смысл идентификаторам», а также безопасность типов. Вы можете ясно сказать, что «xDeleted» относится к «RecordType» и представляет «тип записи» (вау!) Даже спустя годы. Для этого константы потребуют комментариев, а также перехода вверх и вниз в коде.
источник
Не обязательно...
Не обязательно - но вы должны быть явными в точках хранения ...
Вы можете создавать операторы, чтобы избавиться от этого:
То же самое может произойти с любым из этих механизмов: проверки диапазонов и значений обычно ортогональны типобезопасности (хотя пользовательские типы - то есть ваши собственные классы - могут обеспечивать "инварианты" своих данных). С перечислениями компилятор может выбрать более крупный тип для размещения значений, а неинициализированная, поврежденная или просто неправильно установленная переменная перечисления все равно может в конечном итоге интерпретировать свой битовый шаблон как число, которого вы не ожидаете - сравнение неравных с любым из идентификаторы перечисления, любая их комбинация и 0.
Что ж, в конце концов, испытанное и надежное побитовое ИЛИ перечислений в стиле C работает очень хорошо, если у вас есть битовые поля и пользовательские операторы на картинке. Вы можете дополнительно улучшить свою надежность с помощью некоторых пользовательских функций проверки и утверждений, как в ответе mat_geek; методы, которые в равной степени применимы к обработке строковых, int, double значений и т. д.
Вы можете возразить, что это «чище»:
Мне безразлично: биты данных упаковываются плотнее, но код значительно увеличивается ... зависит от того, сколько у вас объектов, а lamdbas - какими бы красивыми они ни были - по-прежнему более беспорядочные и трудные для правильного выполнения, чем побитовые ИЛИ.
BTW / - аргумент о довольно слабой безопасности потоков IMHO - лучше всего помнить как фоновое соображение, а не стать доминирующей движущей силой решения; совместное использование мьютексов между битовыми полями является более вероятной практикой, даже если не знать об их упаковке (мьютексы являются относительно громоздкими элементами данных - мне нужно действительно беспокоиться о производительности, чтобы рассмотреть возможность наличия нескольких мьютексов на элементах одного объекта, и я бы внимательно посмотрел достаточно, чтобы заметить, что это были битовые поля). У любого типа размером с вложенное слово может быть такая же проблема (например, а
uint8_t
). В любом случае вы можете попробовать операции в стиле атомарного сравнения и замены, если вам отчаянно нужен более высокий уровень параллелизма.источник
operator|
должен быть приведен к целочисленному типу (unsigned int
)|
. В противном случае онoperator|
будет рекурсивно вызывать себя и вызывать переполнение стека во время выполнения. Я предлагаю:return RecordType( unsigned(lhs) | unsigned(rhs) );
. CheersДаже если вам нужно использовать 4 байта для хранения перечисления (я не так хорошо знаком с C ++ - я знаю, что вы можете указать базовый тип в C #), это все равно того стоит - используйте перечисления.
В наше время серверов с гигабайтами памяти такие вещи, как 4 байта против 1 байта памяти на уровне приложения в целом не имеют значения. Конечно, если в вашей конкретной ситуации использование памяти так важно (и вы не можете заставить C ++ использовать байт для поддержки перечисления), вы можете рассмотреть маршрут 'static const'.
В конце концов, вы должны спросить себя, стоит ли использовать «static const» для экономии памяти 3 байта для вашей структуры данных?
Следует иметь в виду еще кое-что - IIRC, на x86, структуры данных выровнены по 4 байта, поэтому, если у вас нет нескольких элементов ширины байта в структуре «записи», это может не иметь значения. Протестируйте и убедитесь, что это так, прежде чем идти на компромисс между ремонтопригодностью и производительностью / пространством.
источник
int
если оно не слишком мало». [Если вы не укажете базовый тип в C ++ 11, он будет использовать устаревшее поведение. И наоборот,enum class
базовый тип C ++ 11 явно использует значение по умолчанию,int
если не указано иное.]Если вам нужна безопасность типов классов с удобством синтаксиса перечисления и проверки битов, рассмотрите безопасные метки в C ++ . Я работал с автором, он довольно умен.
Однако будьте осторожны. В конце концов, этот пакет использует шаблоны и макросы!
источник
Вам действительно нужно передавать значения флагов как концептуальное целое, или у вас будет много кода для каждого флага? В любом случае, я думаю, что наличие этого как класса или структуры 1-битных битовых полей может быть более ясным:
Тогда ваш класс записи может иметь переменную-член struct RecordFlag, функции могут принимать аргументы типа struct RecordFlag и т. Д. Компилятор должен упаковывать битовые поля вместе, экономя место.
источник
Я бы, вероятно, не стал использовать перечисление для такого рода вещей, где значения могут быть объединены вместе, чаще всего перечисления являются взаимоисключающими состояниями.
Но какой бы метод вы ни использовали, чтобы было более ясно, что это значения, которые представляют собой биты, которые можно комбинировать вместе, вместо этого используйте этот синтаксис для фактических значений:
Использование сдвига влево помогает указать, что каждое значение должно быть одним битом, маловероятно, что позже кто-то сделает что-то неправильно, например, добавит новое значение и присвоит ему значение 9.
источник
Основываясь на KISS , высокой когезии и низкой связи , задайте следующие вопросы:
Есть отличная книга « Крупномасштабный дизайн программного обеспечения C ++ », в которой внешние типы продвигают базовые типы, если вы можете избежать другой зависимости файла заголовка / интерфейса, которую вы должны попробовать.
источник
Если вы используете Qt, вам следует поискать QFlags . Класс QFlags обеспечивает безопасный для типов способ хранения ИЛИ-комбинаций значений перечисления.
источник
Я бы предпочел пойти с
Просто потому что:
источник
Не то чтобы мне нравилось все перестраивать, но иногда в этих случаях может быть стоит создать (небольшой) класс для инкапсуляции этой информации. Если вы создадите класс RecordType, он может иметь такие функции, как:
void setDeleted ();
void clearDeleted ();
bool isDeleted ();
и т. д. (или что угодно по соглашению)
Он может проверять комбинации (в случае, когда не все комбинации допустимы, например, если «новый» и «удаленный» не могут быть установлены одновременно). Если вы просто использовали битовые маски и т. Д., Тогда код, который устанавливает состояние, необходимо проверить, класс также может инкапсулировать эту логику.
Класс также может дать вам возможность прикреплять значимую информацию о журнале к каждому состоянию, вы можете добавить функцию для возврата строкового представления текущего состояния и т. Д. (Или использовать операторы потоковой передачи '<<').
Тем не менее, если вы беспокоитесь о хранилище, вы все равно можете иметь класс только с элементом данных char, поэтому возьмите только небольшой объем хранилища (при условии, что он не виртуальный). Конечно, в зависимости от оборудования и т. Д. У вас могут возникнуть проблемы с выравниванием.
Фактические значения битов могут быть невидимы для остального «мира», если они находятся в анонимном пространстве имен внутри файла cpp, а не в файле заголовка.
Если вы обнаружите, что код, использующий enum / # define / bitmask и т. Д., Имеет много «вспомогательного» кода для работы с недопустимыми комбинациями, журналированием и т. Д., Возможно, стоит подумать об инкапсуляции в классе. Конечно, в большинстве случаев простые проблемы лучше решать простыми ...
источник