enum X : int
(С #) или enum class X : int
(C ++ , 11) представляет собой тип , который имеет скрытое внутреннее поле , int
который может содержать любое значение. Кроме того, X
в перечислении определен ряд предопределенных констант . Можно привести перечисление к его целочисленному значению и наоборот. Это все верно как в C #, так и в C ++ 11.
В C # перечисления используются не только для хранения отдельных значений, но и для хранения побитовых комбинаций флагов в соответствии с рекомендацией Microsoft . Такие перечисления (обычно, но не обязательно) украшены [Flags]
атрибутом. Чтобы упростить жизнь разработчикам, побитовые операторы (OR, AND и т. Д.) Перегружены, так что вы можете легко сделать что-то вроде этого (C #):
void M(NumericType flags);
M(NumericType.Sign | NumericType.ZeroPadding);
Я опытный разработчик C #, но программирую на C ++ только пару дней, и я не знаком с соглашениями C ++. Я намерен использовать перечисление C ++ 11 точно так же, как я это делал в C #. В C ++ 11 побитовые операторы в перечисленных областях не перегружены, поэтому я хотел их перегрузить .
Это вызвало дебаты, и мнения, кажется, различаются между тремя вариантами:
Переменная типа enum используется для хранения битового поля, аналогично C #:
void M(NumericType flags); // With operator overloading: M(NumericType::Sign | NumericType::ZeroPadding); // Without operator overloading: M(static_cast<NumericType>(static_cast<int>(NumericType::Sign) | static_cast<int>(NumericType::ZeroPadding)));
Но это противоречило бы строго типизированной философии перечисления в ограниченных перечислениях C ++ 11.
Используйте простое целое число, если вы хотите сохранить побитовую комбинацию перечислений:
void M(int flags); M(static_cast<int>(NumericType::Sign) | static_cast<int>(NumericType::ZeroPadding));
Но это сведет все к a
int
, оставляя вас без понятия, какой тип вы должны использовать в методе.Напишите отдельный класс, который будет перегружать операторы и содержать побитовые флаги в скрытом целочисленном поле:
class NumericTypeFlags { unsigned flags_; public: NumericTypeFlags () : flags_(0) {} NumericTypeFlags (NumericType t) : flags_(static_cast<unsigned>(t)) {} //...define BITWISE test/set operations }; void M(NumericTypeFlags flags); M(NumericType::Sign | NumericType::ZeroPadding);
( Полный код по user315052 )
Но тогда у вас нет IntelliSense или какой-либо другой поддержки, которая намекает вам на возможные значения.
Я знаю, что это субъективный вопрос , но: какой подход я должен использовать? Какой подход, если таковой имеется, наиболее широко известен в C ++? Какой подход вы используете при работе с битовыми полями и почему ?
Конечно, поскольку все три подхода работают, я ищу фактические и технические причины, общепринятые соглашения, а не просто личные предпочтения.
Например, из-за моего фона C # я склоняюсь к подходу 1 в C ++. Это дает дополнительное преимущество, заключающееся в том, что моя среда разработки может подсказывать мне возможные значения, а с перегруженными операторами enum это легко написать и понять, и довольно чисто. И подпись метода ясно показывает, какую ценность он ожидает. Но большинство людей здесь не согласны со мной, вероятно, по уважительной причине.
источник
enum E { A = 1, B = 2, C = 4, };
, диапазон составляет0..7
(3 бита). Таким образом, стандарт C ++ явно гарантирует, что # 1 всегда будет жизнеспособным вариантом. [В частности, поenum class
умолчанию используется,enum class : int
если не указано иное, и, следовательно, всегда имеет фиксированный базовый тип.])Ответы:
Самый простой способ - обеспечить перегрузку оператора самостоятельно. Я думаю о создании макроса для расширения базовых перегрузок для каждого типа.
(Обратите внимание, что
type_traits
это заголовок C ++ 11 иstd::underlying_type_t
функция C ++ 14.)источник
static_cast<T>
для ввода, но C-стиль приведен для результата здесь?SBJFrameDrag
он определен в классе, а|
оператор-впоследствии используется в определениях того же класса, как бы вы определили оператор так, чтобы его можно было использовать в классе?Исторически я всегда использовал старое (слабо типизированное) перечисление для именования битовых констант и просто явно использовал класс хранения для хранения результирующего флага. Здесь я должен был бы взять на себя ответственность за то, чтобы мои перечисления соответствовали типу хранилища, и отслеживать связь между полем и связанными с ним константами.
Мне нравится идея строго типизированных перечислений, но мне не очень нравится идея, что переменные перечислимого типа могут содержать значения, которые не входят в число констант этого перечисления.
Например, предполагая побитовый или был перегружен:
Для третьего варианта вам понадобится шаблон для извлечения типа хранилища перечисления. Предполагая, что мы хотим форсировать неподписанный базовый тип (мы можем обработать и подписанный, с небольшим количеством кода):
Это все еще не дает вам IntelliSense или автозаполнения, но обнаружение типа хранилища менее уродливо, чем я первоначально ожидал.
Теперь я нашел альтернативу: вы можете указать тип хранилища для слабо типизированного перечисления. Он даже имеет тот же синтаксис, что и в C #
Поскольку он слабо типизирован и неявно преобразуется в / из int (или любого типа хранилища, который вы выбираете), он чувствует себя менее странно, если значения не совпадают с перечисленными константами.
Недостатком является то, что это описывается как «переходный» ...
NB. этот вариант добавляет свои перечисляемые константы как во вложенную, так и во вложенную область, но вы можете обойти это с помощью пространства имен:
источник
Вы можете определить безопасные для типов флаги перечисления в C ++ 11, используя
std::enable_if
. Это элементарная реализация, в которой могут отсутствовать некоторые вещи:Обратите вниманиеnumber_of_bits
, что компилятор, к сожалению, не может быть заполнен, так как C ++ не имеет никакого способа сделать интроспективный анализ возможных значений перечисления.Редактировать: На самом деле я исправлен, возможно получить компилятор
number_of_bits
для вас.Обратите внимание, что это может обрабатывать (крайне неэффективно) непостоянный диапазон значений перечисления. Давайте просто скажем, что не стоит использовать вышеприведенное с перечислением, как это, иначе начнется безумие:
Но, учитывая все обстоятельства, в конце концов, это вполне пригодное решение. Не нуждается в каких-либо поточках на стороне пользователя, безопасен по типу и в пределах своих возможностей, настолько эффективен, насколько это возможно (я сильно полагаюсь на
std::bitset
качество реализации здесь;)
).источник
я
ненавидетьненавижу макросы в моем C ++ 14 столько же, сколько следующий парень, но я привык использовать это повсеместно, и довольно либерально тоже:Сделать так просто, как
И, как говорится, доказательство в пудинге:
Не стесняйтесь определять любой из отдельных операторов по своему усмотрению, но, по моему мнению, C / C ++ предназначен для взаимодействия с низкоуровневыми концепциями и потоками, и вы можете вырвать эти побитовые операторы из моих холодных мертвых рук. и я буду драться с вами всеми нечестивыми макросами и переворачивающими биты заклинаниями, которые я могу призвать, чтобы сохранить их.
источник
std::enable_if
с,std::is_enum
чтобы ограничить ваши свободные перегрузки операторов только для работы с перечисляемыми типами. Я также добавил операторы сравнения (используяstd::underlying_type
) и логический оператор not для дальнейшего сокращения разрыва без потери строгой типизации. Единственное , что я не могу соответствовать это неявное преобразование к BOOL, ноflags != 0
и!flags
достаточно для меня.Обычно вы определяете набор целочисленных значений, которые соответствуют однобитовым двоичным числам, а затем складываете их вместе. Так обычно делают программисты на Си.
Таким образом, вы должны иметь (с помощью оператора Bitshift для установки значений, например, 1 << 2 - то же самое, что двоичный код 100)
и т.д
В C ++ у вас есть больше опций, определите новый тип, а не int (используйте typedef ) и установите значения, аналогичные приведенным выше; или определить битовое поле или вектор bools . Последние 2 очень компактны и имеют больше смысла для работы с флагами. Преимущество битового поля состоит в том, что он дает вам проверку типов (и, следовательно, intellisense).
Я бы сказал (очевидно, субъективно), что программист C ++ должен использовать битовое поле для вашей проблемы, но я склонен видеть подход #define, часто используемый программами C в программах C ++.
Я полагаю, что битовое поле наиболее близко к перечислению C #, поэтому C # попытался перегрузить перечисление, чтобы быть типом битового поля, странно - перечисление действительно должно быть типом "одиночный выбор".
источник
0b0100
), так что1 << n
формат устарел.Краткий пример перечисляемых ниже флагов очень похож на C #.
О подходе, на мой взгляд: меньше кода, меньше ошибок, лучше код.
ENUM_FLAGS (T) - это макрос, определенный в enum_flags.h (менее 100 строк, бесплатный для использования без ограничений).
источник
::type
там. Исправлено: paste.ubuntu.com/23884820Есть еще один способ снять кожу с кошки:
Вместо того, чтобы перегружать битовые операторы, по крайней мере некоторые могут предпочесть просто добавить 4 строки, чтобы помочь вам обойти это неприятное ограничение перечислений:
Конечно, вы должны печатать эту
ut_cast()
вещь каждый раз, но, с другой стороны, это дает более читаемый код в том же смысле, что и при использованииstatic_cast<>()
, по сравнению с неявным преобразованием типов илиoperator uint16_t()
другими вещами.И давайте будем честными, использование типа,
Foo
как в приведенном выше коде, имеет свои опасности:Где-то еще кто-то может переключить регистр на переменную
foo
и не ожидать, что он содержит более одного значения ...Так что засорение кода
ut_cast()
помогает предупредить читателей, что происходит что-то подозрительное.источник