Как использовать перечисления в качестве флагов в C ++?


188

Обрабатывать enums как флаги хорошо работает в C # с помощью [Flags]атрибута, но как лучше всего это сделать в C ++?

Например, я хотел бы написать:

enum AnimalFlags
{
    HasClaws = 1,
    CanFly =2,
    EatsFish = 4,
    Endangered = 8
};

seahawk.flags = CanFly | EatsFish | Endangered;

Тем не менее, я получаю ошибки компилятора относительно int/ enumпреобразования. Есть ли лучший способ выразить это, чем просто тупое литье? Предпочтительно, я не хочу полагаться на конструкции из сторонних библиотек, таких как boost или Qt.

РЕДАКТИРОВАТЬ: Как указано в ответах, я могу избежать ошибки компилятора, объявив seahawk.flagsкак int. Однако я хотел бы иметь некоторый механизм для обеспечения безопасности типов, чтобы кто-то не мог писать seahawk.flags = HasMaximizeButton.


Насколько я знаю, в Visual C ++ 2013 [Flags]атрибут работает просто отлично, т.е.:[Flags] enum class FlagBits{ Ready = 1, ReadMode = 2, WriteMode = 4, EOF = 8, Disabled = 16};
rivanov

@rivanov, нет, это не работает с C ++ (2015 тоже). Вы имели в виду C #?
Ajay

5
@rivanov, Атрибут [Flags] работает только с .Net Framework в C ++ CLI, нативный C ++ не поддерживает такие атрибуты.
Золтан Тиринда

Ответы:


252

«Правильный» способ - определить битовые операторы для перечисления, как:

enum AnimalFlags
{
    HasClaws   = 1,
    CanFly     = 2,
    EatsFish   = 4,
    Endangered = 8
};

inline AnimalFlags operator|(AnimalFlags a, AnimalFlags b)
{
    return static_cast<AnimalFlags>(static_cast<int>(a) | static_cast<int>(b));
}

И т.д. Остальные битовые операторы. При необходимости измените, если диапазон enum превышает int.


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

10
Кроме того, является ли приведение из произвольного int обратно в тип enum допустимым, даже если значение int не соответствует ни одному из идентификаторов enum?
Инго Шальк-Шупп

8
Это полная чушь. Какой член AnimalFlagsпредставлен выражением HasClaws | CanFly? Это не то enum, для чего. Используйте целые числа и константы.
Гонки легкости на орбите

26
@LightnessRacesinOrbit: Это не правильно. Домен типа enum является доменом его базового типа - только определенным из них было присвоено имя. И чтобы ответить на ваш вопрос: член " (HasClaws | CanFly)".
Xeo

5
@MarcusJ: ограничение ваших значений степенями 2 позволяет использовать перечисления в качестве битовых флагов. Таким образом, если вы получаете 3, вы знаете, как HasClaws(= 1), так и CanFly(= 2). Если вместо этого вы просто присваиваете значения с 1 по 4 и получаете 3, это может быть одиночная EatsFishили снова комбинация HasClawsи CanFly. Если ваше перечисление обозначает исключительные состояния только тогда, последовательные значения хороши, но комбинация флагов требует, чтобы значения были исключающими бит.
Кристиан Северин

122

Примечание (также немного не по теме): другой способ создания уникальных флагов можно сделать с помощью сдвига битов. Мне самому легче это читать.

enum Flags
{
    A = 1 << 0, // binary 0001
    B = 1 << 1, // binary 0010
    C = 1 << 2, // binary 0100
    D = 1 << 3, // binary 1000
};

Он может содержать значения вплоть до int, то есть, в большинстве случаев, 32 флага, что четко отражается в величине смещения.


2
Не могли бы вы удалить последнюю запятую (3,) и добавить двоеточие после}, чтобы сделать код легким для копирования и вставки? Спасибо
Кату

4
Нет упоминания о шестнадцатеричном? Богохульство!
Pharap

1
@ Джейми, кардиналы всегда начинаются с 1, только ординалы могут начинаться с 0 или 1, в зависимости от того, с кем вы разговариваете.
Майкл

2
@ Майкл, это правда! В перечислении вы обычно резервируете 0 для BLAH_NONE. :-) Спасибо за потрясение этой памяти!
Джейми

1
@Katu • лишняя запятая в окончательном перечислении допускается стандартом. Мне это не нравится, но я уже знаю, что Страуструп сказал бы мне: «Тебе это не нравится? Ну, не стесняйся создавать свой собственный язык. Я сделал».
Eljay

55

Для ленивых людей, как я, вот шаблонное решение для копирования и вставки:

template<class T> inline T operator~ (T a) { return (T)~(int)a; }
template<class T> inline T operator| (T a, T b) { return (T)((int)a | (int)b); }
template<class T> inline T operator& (T a, T b) { return (T)((int)a & (int)b); }
template<class T> inline T operator^ (T a, T b) { return (T)((int)a ^ (int)b); }
template<class T> inline T& operator|= (T& a, T b) { return (T&)((int&)a |= (int)b); }
template<class T> inline T& operator&= (T& a, T b) { return (T&)((int&)a &= (int)b); }
template<class T> inline T& operator^= (T& a, T b) { return (T&)((int&)a ^= (int)b); }

23
+1 Лень - одно из трех великих достоинств программиста: threevirtues.com
Pharap

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

@Rai: вы всегда можете поместить его в пространство имен и, usingгде это уместно, просто так rel_ops.
Яков Галка

1
@ybungalobill, но у вас все еще будет та же проблема с операциями, применяемыми к любому типу в области применения, который предположительно будет соответствовать перечислению? Я думаю, что черты, скорее всего, необходимы.
Рай

19
Не используйте этот код. Это открывает дверь для ЛЮБОГО класса, который будет эксплуатироваться по ошибке. Также код использует приведение в старом стиле, которое не будет проходить через строгую компиляцию GCC shitalshah.com/p/… .
Шиталь Шах

44

Обратите внимание: если вы работаете в среде Windows, DEFINE_ENUM_FLAG_OPERATORSв winnt.h определен макрос, который сделает эту работу за вас. Так что в этом случае вы можете сделать это:

enum AnimalFlags
{
    HasClaws = 1,
    CanFly =2,
    EatsFish = 4,
    Endangered = 8
};
DEFINE_ENUM_FLAG_OPERATORS(AnimalFlags)

seahawk.flags = CanFly | EatsFish | Endangered;

44

К какому типу относится переменная seahawk.flags?

В стандартном C ++ перечисления не являются типобезопасными. Они эффективно целые числа.

AnimalFlags НЕ должен быть типом вашей переменной. Ваша переменная должна быть int и ошибка исчезнет.

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

Перечисляемые значения являются типом int по умолчанию. Таким образом, вы можете, конечно, побитовый ИЛИ объединить их и собрать их вместе и сохранить результат в int.

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

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

РЕДАКТИРОВАТЬ: автор сказал, что они были обеспокоены безопасностью типов и они не хотят, чтобы значение, которое не должно существовать внутри типа int.

Но было бы небезопасно помещать значение вне диапазона AnimalFlags внутри переменной типа AnimalFlags.

Существует безопасный способ проверки значений вне диапазона, хотя внутри типа int ...

int iFlags = HasClaws | CanFly;
//InvalidAnimalFlagMaxValue-1 gives you a value of all the bits 
// smaller than itself set to 1
//This check makes sure that no other bits are set.
assert(iFlags & ~(InvalidAnimalFlagMaxValue-1) == 0);

enum AnimalFlags {
    HasClaws = 1,
    CanFly =2,
    EatsFish = 4,
    Endangered = 8,

    // put new enum values above here
    InvalidAnimalFlagMaxValue = 16
};

Вышеприведенное не мешает вам поставить неверный флаг из другого перечисления со значением 1,2,4 или 8.

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

C ++ 0x примечание: строго типизированные перечисления

В C ++ 0x вы, наконец, можете получить безопасные для перечисления значения enum ....

enum class AnimalFlags {
    CanFly = 2,
    HasClaws = 4
};

if(CanFly == 2) { }//Compiling error

4
Значения перечисления не являются целыми числами, но они очень легко преобразуются в целые числа. Тип HasClaws | CanFly- это некоторый целочисленный тип, но тип HasClaws- это AnimalFlagsне целочисленный тип.
Кару

1
Ах, но что, если мы определим правильный диапазон перечисления, который будет не только отдельным значением флага, но также и их побитовой комбинацией. Тогда ответ Эйдолона является правильным и утверждает, что в качестве этого типа могут быть переданы только комбинации правильного перечисления флага.
Скотт

3
@Scott: Стоит отметить, что стандарт C ++ таким образом определяет допустимый диапазон значений экземпляра enum. «для перечисления, где emin - наименьший перечислитель, а emax - наибольший, значения перечисления - это значения в диапазоне от bmin до bmax, определяемые следующим образом: пусть K будет 1 для представления дополнения до двух и 0 для единицы ' дополнение или представление величины знака. bmax - наименьшее значение, большее или равное max(|emin| − K, |emax|)и равное (1u<<M) - 1, где Mнеотрицательное целое число. "
Бен Фойгт

Для тех, кто (как и я) просто хочет чего-то практичного, которое позволяет побитовым манипулировать значениями перечисления и не выглядит слишком уродливо с шаблонами и приведением типов, это хорошее решение; просто определите переменные для типа int.
Эрик Соколовский

Также обратите внимание на то, что в C ++ регулярный тип по enumумолчанию технически не является intбазовым типом (либо до C ++ 11 (IIRC), либо после C ++ 11, когда не указан базовый тип), хотя enum class это так и есть . Вместо этого базовый тип по умолчанию равен чему-то достаточно большому, чтобы представлять все перечислители, с единственным реальным жестким правилом, которое только больше, чем intесли бы оно явно требовалось . По сути, базовый тип указывается как (перефразировано) «все, что работает, но, вероятно, int если перечислители не слишком велики для int».
Джастин Тайм - Восстановить Монику

26

Я считаю, что принятый в настоящее время ответ Эйдолона слишком опасен. Оптимизатор компилятора может делать предположения о возможных значениях в перечислении, и вы можете получить мусор обратно с недопустимыми значениями. И обычно никто не хочет определять все возможные перестановки в перечислениях флагов.

Как утверждает Брайан Р. Бонди ниже, если вы используете C ++ 11 (что должно быть всем, это хорошо), теперь вы можете сделать это проще с помощью enum class:

enum class ObjectType : uint32_t
{
    ANIMAL = (1 << 0),
    VEGETABLE = (1 << 1),
    MINERAL = (1 << 2)
};


constexpr enum ObjectType operator |( const enum ObjectType selfValue, const enum ObjectType inValue )
{
    return (enum ObjectType)(uint32_t(selfValue) | uint32_t(inValue));
}

// ... add more operators here. 

Это обеспечивает стабильный диапазон размеров и значений путем указания типа для перечисления, запрещает автоматическое преобразование перечислений в целочисленные значения и т. Д. С помощью enum classи использует constexprдля обеспечения того, чтобы код для операторов вставлялся и, следовательно, так же быстро, как и обычные числа.

Для людей, придерживающихся диалектов C ++ до 11

Если бы я застрял с компилятором, который не поддерживает C ++ 11, я бы пошел с переносом типа int в класс, который затем разрешает использовать только побитовые операторы и типы из этого перечисления для установки его значений:

template<class ENUM,class UNDERLYING=typename std::underlying_type<ENUM>::type>
class SafeEnum
{
public:
    SafeEnum() : mFlags(0) {}
    SafeEnum( ENUM singleFlag ) : mFlags(singleFlag) {}
    SafeEnum( const SafeEnum& original ) : mFlags(original.mFlags) {}

    SafeEnum&   operator |=( ENUM addValue )    { mFlags |= addValue; return *this; }
    SafeEnum    operator |( ENUM addValue )     { SafeEnum  result(*this); result |= addValue; return result; }
    SafeEnum&   operator &=( ENUM maskValue )   { mFlags &= maskValue; return *this; }
    SafeEnum    operator &( ENUM maskValue )    { SafeEnum  result(*this); result &= maskValue; return result; }
    SafeEnum    operator ~()    { SafeEnum  result(*this); result.mFlags = ~result.mFlags; return result; }
    explicit operator bool()                    { return mFlags != 0; }

protected:
    UNDERLYING  mFlags;
};

Вы можете определить это как обычный enum + typedef:

enum TFlags_
{
    EFlagsNone  = 0,
    EFlagOne    = (1 << 0),
    EFlagTwo    = (1 << 1),
    EFlagThree  = (1 << 2),
    EFlagFour   = (1 << 3)
};

typedef SafeEnum<enum TFlags_>  TFlags;

И использование также похоже:

TFlags      myFlags;

myFlags |= EFlagTwo;
myFlags |= EFlagThree;

if( myFlags & EFlagTwo )
    std::cout << "flag 2 is set" << std::endl;
if( (myFlags & EFlagFour) == EFlagsNone )
    std::cout << "flag 4 is not set" << std::endl;

И вы также можете переопределить базовый тип для двоично-устойчивых перечислений (как C ++ 11 - х enum foo : type) с помощью второго параметра шаблона, то есть typedef SafeEnum<enum TFlags_,uint8_t> TFlags;.

Я пометил operator boolпереопределение explicitключевым словом C ++ 11, чтобы оно не приводило к преобразованиям int, так как это может привести к тому, что наборы флагов в конечном итоге свернуты в 0 или 1 при их записи. Если вы не можете использовать C ++ 11, не используйте эту перегрузку и переписайте первое условие в примере использования как (myFlags & EFlagTwo) == EFlagTwo.


Как примечание, я бы рекомендовал, чтобы примерный оператор, определенный в начале, использовал std::underlying_typeвместо жесткого кодирования определенный тип, или чтобы базовый тип предоставлялся и использовался как псевдоним типа, а не напрямую. Таким образом, изменения в базовом типе будут распространяться автоматически, а не вручную.
Джастин Тайм - Восстановить Монику

17

Самый простой способ сделать это, как показано здесь , с использованием стандартного набора классов библиотеки .

Чтобы эмулировать функцию C # безопасным для типов способом, вам нужно написать оболочку шаблона вокруг набора битов, заменив аргументы int на перечисление, заданное в качестве параметра типа для шаблона. Что-то вроде:

    template <class T, int N>
class FlagSet
{

    bitset<N> bits;

    FlagSet(T enumVal)
    {
        bits.set(enumVal);
    }

    // etc.
};

enum MyFlags
{
    FLAG_ONE,
    FLAG_TWO
};

FlagSet<MyFlags, 2> myFlag;

4
Посмотрите на это для более полного кода: codereview.stackexchange.com/questions/96146/…
Shah

11

На мой взгляд, ни один из ответов пока не идеален. Чтобы быть идеальным, я ожидал бы решения:

  1. Поддерживайте ==, !=, =, &, &=, |, |=и ~операторы в обычном смысле (то есть a & b)
  2. Быть безопасным по типу, т.е. не разрешать присваивать неперечисляемые значения, такие как литералы или целочисленные типы (кроме побитовых комбинаций перечисляемых значений), или разрешать присваивать переменную перечисления целочисленному типу
  3. Разрешить выражения, такие как if (a & b)...
  4. Не требует злых макросов, реализации специфических функций или других взломов

Пока что большинство решений относятся к пунктам 2 или 3. На мой взгляд, WebDancer - закрытие, но в пункте 3 он терпит неудачу и должен повторяться для каждого перечисления.

Мое предлагаемое решение - это обобщенная версия WebDancer, которая также касается пункта 3:

#include <cstdint>
#include <type_traits>

template<typename T = typename std::enable_if<std::is_enum<T>::value, T>::type>
class auto_bool
{
    T val_;
public:
    constexpr auto_bool(T val) : val_(val) {}
    constexpr operator T() const { return val_; }
    constexpr explicit operator bool() const
    {
        return static_cast<std::underlying_type_t<T>>(val_) != 0;
    }
};

template <typename T = typename std::enable_if<std::is_enum<T>::value, T>::type>
constexpr auto_bool<T> operator&(T lhs, T rhs)
{
    return static_cast<T>(
        static_cast<typename std::underlying_type<T>::type>(lhs) &
        static_cast<typename std::underlying_type<T>::type>(rhs));
}

template <typename T = typename std::enable_if<std::is_enum<T>::value, T>::type>
constexpr T operator|(T lhs, T rhs)
{
    return static_cast<T>(
        static_cast<typename std::underlying_type<T>::type>(lhs) |
        static_cast<typename std::underlying_type<T>::type>(rhs));
}

enum class AnimalFlags : uint8_t 
{
    HasClaws = 1,
    CanFly = 2,
    EatsFish = 4,
    Endangered = 8
};

enum class PlantFlags : uint8_t
{
    HasLeaves = 1,
    HasFlowers = 2,
    HasFruit = 4,
    HasThorns = 8
};

int main()
{
    AnimalFlags seahawk = AnimalFlags::CanFly;        // Compiles, as expected
    AnimalFlags lion = AnimalFlags::HasClaws;         // Compiles, as expected
    PlantFlags rose = PlantFlags::HasFlowers;         // Compiles, as expected
//  rose = 1;                                         // Won't compile, as expected
    if (seahawk != lion) {}                           // Compiles, as expected
//  if (seahawk == rose) {}                           // Won't compile, as expected
//  seahawk = PlantFlags::HasThorns;                  // Won't compile, as expected
    seahawk = seahawk | AnimalFlags::EatsFish;        // Compiles, as expected
    lion = AnimalFlags::HasClaws |                    // Compiles, as expected
           AnimalFlags::Endangered;
//  int eagle = AnimalFlags::CanFly |                 // Won't compile, as expected
//              AnimalFlags::HasClaws;
//  int has_claws = seahawk & AnimalFlags::CanFly;    // Won't compile, as expected
    if (seahawk & AnimalFlags::CanFly) {}             // Compiles, as expected
    seahawk = seahawk & AnimalFlags::CanFly;          // Compiles, as expected

    return 0;
}

Это создает перегрузки необходимых операторов, но использует SFINAE, чтобы ограничить их перечисляемыми типами. Обратите внимание, что в интересах краткости я не определил все операторы, но отличается только один &. В настоящее время операторы являются глобальными (т.е. применяются ко всем перечисляемым типам), но это можно уменьшить либо путем помещения перегрузок в пространство имен (что я делаю), либо путем добавления дополнительных условий SFINAE (возможно, с использованием определенных базовых типов или специально созданных псевдонимов типов). ). Это underlying_type_tфункция C ++ 14, но, похоже, она хорошо поддерживается и ее легко эмулировать для C ++ 11 с помощью простогоtemplate<typename T> using underlying_type_t = underlying_type<T>::type;


Хотя предложенное вами решение отлично работает, оно также вводит этот шаблон для перечислений, которые не должны рассматриваться как флаги. Вероятно, это причина использования (злых) макросов, таких как DEFINE_ENUM_FLAG_OPERATORS от Microsoft.
WebDancer

@ WebDancer, вы, конечно, правы, но тогда я уже сказал это в своем ответе. Я также предложил два способа решения проблемы - поместить ее в пространство имен или использовать более ограничивающее условие SFINAE.
Тревор,

Моя точка зрения заключается в том, что если вы не создаете действительно узкое пространство имен (например, пространство имен AllMyFlagEnums) или не имеете условия SFINAE, которое каким-то образом выбирает только несколько точных перечислений, код на мой взгляд нарушен. Вместо того, чтобы рисковать этим, я копирую и вставляю «текстовый шаблон», где просто заменяю имя enum, а иногда и «злые» макросы. Я бы хотел, чтобы был лучший способ.
WebDancer

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

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

8

Стандарт C ++ прямо говорит об этом, см. Раздел «17.5.2.1.3 Типы битовых масок»:

http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2012/n3485.pdf

Учитывая этот «шаблон» вы получаете:

enum AnimalFlags : unsigned int
{
    HasClaws = 1,
    CanFly = 2,
    EatsFish = 4,
    Endangered = 8
};

constexpr AnimalFlags operator|(AnimalFlags X, AnimalFlags Y) {
    return static_cast<AnimalFlags>(
        static_cast<unsigned int>(X) | static_cast<unsigned int>(Y));
}

AnimalFlags& operator|=(AnimalFlags& X, AnimalFlags Y) {
    X = X | Y; return X;
}

И похоже на других операторов. Также обратите внимание на «constexpr», он необходим, если вы хотите, чтобы компилятор мог выполнять операторы во время компиляции.

Если вы используете C ++ / CLI и хотите иметь возможность присваивать перечислимым элементам классов ref, вам нужно вместо этого использовать отслеживание ссылок:

AnimalFlags% operator|=(AnimalFlags% X, AnimalFlags Y) {
    X = X | Y; return X;
}

ПРИМЕЧАНИЕ. Этот пример неполон, см. Раздел «17.5.2.1.3 Типы битовой маски» для полного набора операторов.


6

Я обнаружил, что задаю тот же вопрос, и придумал общее решение на C ++ 11, похожее на сору:

template <typename TENUM>
class FlagSet {

private:
    using TUNDER = typename std::underlying_type<TENUM>::type;
    std::bitset<std::numeric_limits<TUNDER>::max()> m_flags;

public:
    FlagSet() = default;

    template <typename... ARGS>
    FlagSet(TENUM f, ARGS... args) : FlagSet(args...)
    {   
        set(f);
    }   
    FlagSet& set(TENUM f)
    {   
        m_flags.set(static_cast<TUNDER>(f));
        return *this;
    }   
    bool test(TENUM f)
    {   
        return m_flags.test(static_cast<TUNDER>(f));
    }   
    FlagSet& operator|=(TENUM f)
    {   
        return set(f);
    }   
};

Интерфейс может быть улучшен по вкусу. Тогда это можно использовать так:

FlagSet<Flags> flags{Flags::FLAG_A, Flags::FLAG_C};
flags |= Flags::FLAG_D;

2
Посмотрите на это для лучшего и более полного кода: codereview.stackexchange.com/questions/96146/…
Shah

5
За исключением моего использования numeric_limits, код почти такой же. Я предполагаю, что это распространенный способ иметь класс enum, безопасный для типов. Я бы сказал, что лучше использовать numeric_limits, чем ставить SENTINEL в конце каждого перечисления.
Омаир

1
Это огромный бит!
Гонки

(потенциально ...)
Гонки

5

Если ваш компилятор еще не поддерживает строго типизированные перечисления, вы можете посмотреть следующую статью из источника c ++:

Из аннотации:

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


5

Я использую следующий макрос:

#define ENUM_FLAG_OPERATORS(T)                                                                                                                                            \
    inline T operator~ (T a) { return static_cast<T>( ~static_cast<std::underlying_type<T>::type>(a) ); }                                                                       \
    inline T operator| (T a, T b) { return static_cast<T>( static_cast<std::underlying_type<T>::type>(a) | static_cast<std::underlying_type<T>::type>(b) ); }                   \
    inline T operator& (T a, T b) { return static_cast<T>( static_cast<std::underlying_type<T>::type>(a) & static_cast<std::underlying_type<T>::type>(b) ); }                   \
    inline T operator^ (T a, T b) { return static_cast<T>( static_cast<std::underlying_type<T>::type>(a) ^ static_cast<std::underlying_type<T>::type>(b) ); }                   \
    inline T& operator|= (T& a, T b) { return reinterpret_cast<T&>( reinterpret_cast<std::underlying_type<T>::type&>(a) |= static_cast<std::underlying_type<T>::type>(b) ); }   \
    inline T& operator&= (T& a, T b) { return reinterpret_cast<T&>( reinterpret_cast<std::underlying_type<T>::type&>(a) &= static_cast<std::underlying_type<T>::type>(b) ); }   \
    inline T& operator^= (T& a, T b) { return reinterpret_cast<T&>( reinterpret_cast<std::underlying_type<T>::type&>(a) ^= static_cast<std::underlying_type<T>::type>(b) ); }

Он похож на упомянутые выше, но имеет несколько улучшений:

  • Это безопасный тип (он не предполагает, что базовый тип является int)
  • Не требует указания вручную базового типа (в отличие от ответа @LunarEclipse)

Нужно включить type_traits:

#include <type_traits>

4

Я хотел бы остановиться на ответе Uliwitness , исправляя его код для C ++ 98 и используя идиому Safe Bool , из-за отсутствия std::underlying_type<>шаблона и explicitключевого слова в версиях C ++ ниже C ++ 11.

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

enum AnimalFlags_
{
    HasClaws,
    CanFly,
    EatsFish,
    Endangered
};
typedef FlagsEnum<AnimalFlags_> AnimalFlags;

seahawk.flags = AnimalFlags() | CanFly | EatsFish | Endangered;

Затем вы можете получить значение необработанных флагов с помощью

seahawk.flags.value();

Вот код

template <typename EnumType, typename Underlying = int>
class FlagsEnum
{
    typedef Underlying FlagsEnum::* RestrictedBool;

public:
    FlagsEnum() : m_flags(Underlying()) {}

    FlagsEnum(EnumType singleFlag):
        m_flags(1 << singleFlag)
    {}

    FlagsEnum(const FlagsEnum& original):
        m_flags(original.m_flags)
    {}

    FlagsEnum& operator |=(const FlagsEnum& f) {
        m_flags |= f.m_flags;
        return *this;
    }

    FlagsEnum& operator &=(const FlagsEnum& f) {
        m_flags &= f.m_flags;
        return *this;
    }

    friend FlagsEnum operator |(const FlagsEnum& f1, const FlagsEnum& f2) {
        return FlagsEnum(f1) |= f2;
    }

    friend FlagsEnum operator &(const FlagsEnum& f1, const FlagsEnum& f2) {
        return FlagsEnum(f1) &= f2;
    }

    FlagsEnum operator ~() const {
        FlagsEnum result(*this);
        result.m_flags = ~result.m_flags;
        return result;
    }

    operator RestrictedBool() const {
        return m_flags ? &FlagsEnum::m_flags : 0;
    }

    Underlying value() const {
        return m_flags;
    }

protected:
    Underlying  m_flags;
};

3

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

struct AnimalProperties
{
    bool HasClaws : 1;
    bool CanFly : 1;
    bool EatsFish : 1;
    bool Endangered : 1;
};

union AnimalDescription
{
    AnimalProperties Properties;
    int Flags;
};

void TestUnionFlags()
{
    AnimalDescription propertiesA;
    propertiesA.Properties.CanFly = true;

    AnimalDescription propertiesB = propertiesA;
    propertiesB.Properties.EatsFish = true;

    if( propertiesA.Flags == propertiesB.Flags )
    {
        cout << "Life is terrible :(";
    }
    else
    {
        cout << "Life is great!";
    }

    AnimalDescription propertiesC = propertiesA;
    if( propertiesA.Flags == propertiesC.Flags )
    {
        cout << "Life is great!";
    }
    else
    {
        cout << "Life is terrible :(";
    }
}

Мы видим, что жизнь прекрасна, у нас есть свои дискретные ценности, и у нас есть хороший int для & и | к нашему сердцу содержание, которое все еще имеет контекст того, что означают его биты. Все непротиворечиво и предсказуемо ... для меня ... пока я продолжаю использовать компилятор Microsoft VC ++ с обновлением 3 на Win10 x64 и не трогаю флаги моего компилятора :)

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

class AnimalDefinition {
public:
    static AnimalDefinition *GetAnimalDefinition( AnimalFlags flags );   //A little too obvious for my taste... NEXT!
    static AnimalDefinition *GetAnimalDefinition( AnimalProperties properties );   //Oh I see how to use this! BORING, NEXT!
    static AnimalDefinition *GetAnimalDefinition( int flags ); //hmm, wish I could see how to construct a valid "flags" int without CrossFingers+Ctrl+Shift+F("Animal*"). Maybe just hard-code 16 or something?

    AnimalFlags animalFlags;  //Well this is *way* too hard to break unintentionally, screw this!
    int flags; //PERFECT! Nothing will ever go wrong here... 
    //wait, what values are used for this particular flags field? Is this AnimalFlags or ObjectFlags? Or is it RuntimePlatformFlags? Does it matter? Where's the documentation? 
    //Well luckily anyone in the code base and get confused and destroy the whole program! At least I don't need to static_cast anymore, phew!

    private:
    AnimalDescription m_description; //Oh I know what this is. All of the mystery and excitement of life has been stolen away :(
}

Итак, вы делаете свою декларацию union закрытой, чтобы предотвратить прямой доступ к «Flags», и должны добавить геттеры / сеттеры и перегрузки операторов, а затем создать макрос для всего этого, и вы в основном вернулись к тому, с чего начинали, когда пытались сделать это с помощью Enum.

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

Во время выполнения вы можете играть трюки с установкой полей и установкой флагов XOR, чтобы увидеть, какие биты изменились, для меня это звучит довольно глупо, хотя стихи имеют 100% согласованное, независимое от платформы и полностью детерминированное решение, например: ENUM.

TL; DR: не слушайте ненавистников. C ++ не английский. Тот факт, что буквальное определение сокращенного ключевого слова, унаследованного от C, может не подходить для вашего использования, не означает, что вы не должны использовать его, когда определение ключевого слова на C и C ++ полностью включает ваш вариант использования. Вы также можете использовать структуры, чтобы моделировать вещи, отличные от структур, и классы для вещей, отличных от школьной и социальной касты. Вы можете использовать float для значений, которые обоснованы. Вы можете использовать char для переменных, которые не являются ни сожженными, ни людьми в романе, пьесе или фильме. Любой программист, который идет в словарь, чтобы определить значение ключевого слова до того, как спецификация языка ... ну, я буду там молчать.

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


3

Только синтаксический сахар. Никаких дополнительных метаданных.

namespace UserRole // grupy
{ 
    constexpr uint8_t dea = 1;
    constexpr uint8_t red = 2;
    constexpr uint8_t stu = 4;
    constexpr uint8_t kie = 8;
    constexpr uint8_t adm = 16;
    constexpr uint8_t mas = 32;
}

Операторы флагов на целочисленном типе просто работают.


ИМХО это лучший ответ. Чистый и простой, легкий синтаксис клиента. Я бы просто использовал «const int», а не «constexpr uint8_t», но концепция та же.
йойо

(извините, "constexpr int")
yoyo

3

В настоящее время нет языковой поддержки флагов enum, мета-классы могут добавить эту функцию, если она когда-либо будет частью стандарта c ++.

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

Файл: EnumClassBitwise.h

#pragma once
#ifndef _ENUM_CLASS_BITWISE_H_
#define _ENUM_CLASS_BITWISE_H_

#include <type_traits>

//unary ~operator    
template <typename Enum, typename std::enable_if_t<std::is_enum<Enum>::value, int> = 0>
constexpr inline Enum& operator~ (Enum& val)
{
    val = static_cast<Enum>(~static_cast<std::underlying_type_t<Enum>>(val));
    return val;
}

// & operator
template <typename Enum, typename std::enable_if_t<std::is_enum<Enum>::value, int> = 0>
constexpr inline Enum operator& (Enum lhs, Enum rhs)
{
    return static_cast<Enum>(static_cast<std::underlying_type_t<Enum>>(lhs) & static_cast<std::underlying_type_t<Enum>>(rhs));
}

// &= operator
template <typename Enum, typename std::enable_if_t<std::is_enum<Enum>::value, int> = 0>
constexpr inline Enum operator&= (Enum& lhs, Enum rhs)
{
    lhs = static_cast<Enum>(static_cast<std::underlying_type_t<Enum>>(lhs) & static_cast<std::underlying_type_t<Enum>>(rhs));
    return lhs;
}

//| operator

template <typename Enum, typename std::enable_if_t<std::is_enum<Enum>::value, int> = 0>
constexpr inline Enum operator| (Enum lhs, Enum rhs)
{
    return static_cast<Enum>(static_cast<std::underlying_type_t<Enum>>(lhs) | static_cast<std::underlying_type_t<Enum>>(rhs));
}
//|= operator

template <typename Enum, typename std::enable_if_t<std::is_enum<Enum>::value, int> = 0>
constexpr inline Enum& operator|= (Enum& lhs, Enum rhs)
{
    lhs = static_cast<Enum>(static_cast<std::underlying_type_t<Enum>>(lhs) | static_cast<std::underlying_type_t<Enum>>(rhs));
    return lhs;
}

#endif // _ENUM_CLASS_BITWISE_H_

Для удобства и уменьшения количества ошибок вы можете заключить операции с битовыми флагами в перечисления, а также в целые числа:

Файл: BitFlags.h

#pragma once
#ifndef _BIT_FLAGS_H_
#define _BIT_FLAGS_H_

#include "EnumClassBitwise.h"

 template<typename T>
 class BitFlags
 {
 public:

     constexpr inline BitFlags() = default;
     constexpr inline BitFlags(T value) { mValue = value; }
     constexpr inline BitFlags operator| (T rhs) const { return mValue | rhs; }
     constexpr inline BitFlags operator& (T rhs) const { return mValue & rhs; }
     constexpr inline BitFlags operator~ () const { return ~mValue; }
     constexpr inline operator T() const { return mValue; }
     constexpr inline BitFlags& operator|=(T rhs) { mValue |= rhs; return *this; }
     constexpr inline BitFlags& operator&=(T rhs) { mValue &= rhs; return *this; }
     constexpr inline bool test(T rhs) const { return (mValue & rhs) == rhs; }
     constexpr inline void set(T rhs) { mValue |= rhs; }
     constexpr inline void clear(T rhs) { mValue &= ~rhs; }

 private:
     T mValue;
 };
#endif //#define _BIT_FLAGS_H_

Возможное использование:

#include <cstdint>
#include <BitFlags.h>
void main()
{
    enum class Options : uint32_t
    { 
          NoOption = 0 << 0
        , Option1  = 1 << 0
        , Option2  = 1 << 1
        , Option3  = 1 << 2
        , Option4  = 1 << 3
    };

    const uint32_t Option1 = 1 << 0;
    const uint32_t Option2 = 1 << 1;
    const uint32_t Option3 = 1 << 2;
    const uint32_t Option4 = 1 << 3;

   //Enum BitFlags
    BitFlags<Options> optionsEnum(Options::NoOption);
    optionsEnum.set(Options::Option1 | Options::Option3);

   //Standard integer BitFlags
    BitFlags<uint32_t> optionsUint32(0);
    optionsUint32.set(Option1 | Option3); 

    return 0;
}

3

@Xaqq предоставил действительно хороший способ типобезопасный использовать перечислений флаги здесь по flag_setклассу.

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

#include "flag_set.hpp"

enum class AnimalFlags : uint8_t {
    HAS_CLAWS,
    CAN_FLY,
    EATS_FISH,
    ENDANGERED,
    _
};

int main()
{
    flag_set<AnimalFlags> seahawkFlags(AnimalFlags::HAS_CLAWS
                                       | AnimalFlags::EATS_FISH
                                       | AnimalFlags::ENDANGERED);

    if (seahawkFlags & AnimalFlags::ENDANGERED)
        cout << "Seahawk is endangered";
}

2

Вы путаете объекты и коллекции объектов. В частности, вы путаете двоичные флаги с наборами двоичных флагов. Правильное решение будет выглядеть так:

// These are individual flags
enum AnimalFlag // Flag, not Flags
{
    HasClaws = 0,
    CanFly,
    EatsFish,
    Endangered
};

class AnimalFlagSet
{
    int m_Flags;

  public:

    AnimalFlagSet() : m_Flags(0) { }

    void Set( AnimalFlag flag ) { m_Flags |= (1 << flag); }

    void Clear( AnimalFlag flag ) { m_Flags &= ~ (1 << flag); }

    bool Get( AnimalFlag flag ) const { return (m_Flags >> flag) & 1; }

};

2

Вот мое решение без необходимости перегрузки или приведения:

namespace EFoobar
{
    enum
    {
        FB_A    = 0x1,
        FB_B    = 0x2,
        FB_C    = 0x4,
    };
    typedef long Flags;
}

void Foobar(EFoobar::Flags flags)
{
    if (flags & EFoobar::FB_A)
        // do sth
        ;
    if (flags & EFoobar::FB_B)
        // do sth
        ;
}

void ExampleUsage()
{
    Foobar(EFoobar::FB_A | EFoobar::FB_B);
    EFoobar::Flags otherflags = 0;
    otherflags|= EFoobar::FB_B;
    otherflags&= ~EFoobar::FB_B;
    Foobar(otherflags);
}

Я думаю, что все в порядке, потому что мы идентифицируем (не строго типизированные) перечисления и целые числа в любом случае.

Так же, как (более длинное) примечание, если вы

  • хотите использовать строго типизированные перечисления и
  • не нужно много возиться со своими флагами
  • производительность не проблема

Я бы придумал это:

#include <set>

enum class EFoobarFlags
{
    FB_A = 1,
    FB_B,
    FB_C,
};

void Foobar(const std::set<EFoobarFlags>& flags)
{
    if (flags.find(EFoobarFlags::FB_A) != flags.end())
        // do sth
        ;
    if (flags.find(EFoobarFlags::FB_B) != flags.end())
        // do sth
        ;
}

void ExampleUsage()
{
    Foobar({EFoobarFlags::FB_A, EFoobarFlags::FB_B});
    std::set<EFoobarFlags> otherflags{};
    otherflags.insert(EFoobarFlags::FB_B);
    otherflags.erase(EFoobarFlags::FB_B);
    Foobar(otherflags);
}

используя списки инициализаторов C ++ 11 и enum class.


Кстати, я бы даже не рекомендовал перечисления для флагов. Простая причина: комбинации флагов снова не являются элементами перечисления. Так что это кажется совершенно неподходящим. В качестве альтернативы я бы использовал using Flags = unsigned longвнутреннее пространство имен или структуру, содержащую сами значения флага, как /*static*/ const Flags XY = 0x01и так далее.
Яу

1

Как указано выше (Кай) или сделайте следующее. Действительно перечисления являются «перечислениями», то, что вы хотите сделать, это иметь набор, поэтому вы должны действительно использовать stl :: set

enum AnimalFlags
{
    HasClaws = 1,
    CanFly =2,
    EatsFish = 4,
    Endangered = 8
};

int main(void)
{
    AnimalFlags seahawk;
    //seahawk= CanFly | EatsFish | Endangered;
    seahawk= static_cast<AnimalFlags>(CanFly | EatsFish | Endangered);
}

1

Может быть, как NS_OPTIONS Objective-C.

#define ENUM(T1, T2) \
enum class T1 : T2; \
inline T1 operator~ (T1 a) { return (T1)~(int)a; } \
inline T1 operator| (T1 a, T1 b) { return static_cast<T1>((static_cast<T2>(a) | static_cast<T2>(b))); } \
inline T1 operator& (T1 a, T1 b) { return static_cast<T1>((static_cast<T2>(a) & static_cast<T2>(b))); } \
inline T1 operator^ (T1 a, T1 b) { return static_cast<T1>((static_cast<T2>(a) ^ static_cast<T2>(b))); } \
inline T1& operator|= (T1& a, T1 b) { return reinterpret_cast<T1&>((reinterpret_cast<T2&>(a) |= static_cast<T2>(b))); } \
inline T1& operator&= (T1& a, T1 b) { return reinterpret_cast<T1&>((reinterpret_cast<T2&>(a) &= static_cast<T2>(b))); } \
inline T1& operator^= (T1& a, T1 b) { return reinterpret_cast<T1&>((reinterpret_cast<T2&>(a) ^= static_cast<T2>(b))); } \
enum class T1 : T2

ENUM(Options, short) {
    FIRST  = 1 << 0,
    SECOND = 1 << 1,
    THIRD  = 1 << 2,
    FOURTH = 1 << 3
};

auto options = Options::FIRST | Options::SECOND;
options |= Options::THIRD;
if ((options & Options::SECOND) == Options::SECOND)
    cout << "Contains second option." << endl;
if ((options & Options::THIRD) == Options::THIRD)
    cout << "Contains third option." << endl;
return 0;

// Output:
// Contains second option. 
// Contains third option.

Можете ли вы объяснить, почему ваш ответ лучше всего подходит? Есть несколько других ответов, которые ответили на этот вопрос, поэтому, пожалуйста, включите некоторую информацию, чтобы отличить ваши.
Trevorp
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.