如何在 C++ 中使用枚举作为标志?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/1448396/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-27 20:00:26  来源:igfitidea点击:

How to use enums as flags in C++?

c++enums

提问by Tony the Pony

Treating enums as flags works nicely in C# via the [Flags]attribute, but what's the best way to do this in C++?

enum通过[Flags]属性将s 视为标志在 C# 中可以很好地工作,但是在 C++ 中执行此操作的最佳方法是什么?

For example, I'd like to write:

例如,我想写:

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

seahawk.flags = CanFly | EatsFish | Endangered;

However, I get compiler errors regarding int/enumconversions. Is there a nicer way to express this than just blunt casting? Preferably, I don't want to rely on constructs from 3rd party libraries such as boost or Qt.

但是,我收到有关int/enum转换的编译器错误。有没有比直接投射更好的方式来表达这一点?最好,我不想依赖来自 3rd 方库(例如 boost 或 Qt)的构造。

EDIT: As indicated in the answers, I can avoid the compiler error by declaring seahawk.flagsas int. However, I'd like to have some mechanism to enforce type safety, so someone can't write seahawk.flags = HasMaximizeButton.

编辑:如答案所示,我可以通过声明seahawk.flagsint. 但是,我想有一些机制来强制执行类型安全,所以有人不能写seahawk.flags = HasMaximizeButton.

回答by eidolon

The "correct" way is to define bit operators for the enum, as:

“正确”的方法是为枚举定义位运算符,如下所示:

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

Etc. rest of the bit operators. Modify as needed if the enum range exceeds int range.

等位运算符的其余部分。如果枚举范围超过 int 范围,则根据需要进行修改。

回答by WoutervD

Note (also a bit off topic): Another way to make unique flags can be done using a bit shift. I, myself, find this easier to read.

注意(也有点离题):另一种制作独特标志的方法可以使用位移位来完成。我,我自己,发现这更容易阅读。

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

It can hold values up to an int so that is, most of the time, 32 flags which is clearly reflected in the shift amount.

它最多可以保存一个 int 值,因此在大多数情况下,32 个标志清楚地反映在移位量中。

回答by user720594

For lazy people like me, here is templated solution to copy&paste:

对于像我这样的懒人,这里是复制和粘贴的模板化解决方案:

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

回答by Simon Mourier

Note if you are working in Windows environment, there is a DEFINE_ENUM_FLAG_OPERATORSmacro defined in winnt.h that does the job for you. So in this case, you can do this:

请注意,如果您在 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;

回答by Brian R. Bondy

What type is the seahawk.flags variable?

seahawk.flags 变量是什么类型?

In standard C++, enumerations are not type-safe. They are effectively integers.

在标准 C++ 中,枚举不是类型安全的。它们实际上是整数。

AnimalFlags should NOT be the type of your variable. Your variable should be int and the error will go away.

AnimalFlags 不应该是你的变量的类型。你的变量应该是 int 并且错误会消失。

Putting hexadecimal values like some other people suggested is not needed. It makes no difference.

不需要像其他人建议的那样放置十六进制值。没有什么不同的。

The enum values ARE of type int by default. So you can surely bitwise OR combine them and put them together and store the result in an int.

默认情况下,枚举值是 int 类型。所以你当然可以按位或组合它们并将它们放在一起并将结果存储在一个整数中。

The enum type is a restricted subset of int whose value is one of its enumerated values. Hence, when you make some new value outside of that range, you can't assign it without casting to a variable of your enum type.

枚举类型是 int 的受限子集,其值是其枚举值之一。因此,当您在该范围之外创建一些新值时,您无法在不强制转换为枚举类型的变量的情况下对其进行分配。

You can also change the enum value types if you'd like, but there is no point for this question.

如果您愿意,您也可以更改枚举值类型,但这个问题没有意义。

EDIT:The poster said they were concerned with type safety and they don't want a value that should not exist inside the int type.

编辑:海报说他们关心类型安全,他们不想要一个不应该存在于 int 类型中的值。

But it would be type unsafe to put a value outside of AnimalFlags's range inside a variable of type AnimalFlags.

但是,将 AnimalFlags 范围之外的值放入类型为 AnimalFlags 的变量中将是类型不安全的。

There is a safe way to check for out of range values though inside the int type...

尽管在 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
};

The above doesn't stop you from putting an invalid flag from a different enum that has the value 1,2,4, or 8 though.

以上并不能阻止您从具有值 1、2、4 或 8 的不同枚举中放置无效标志。

If you want absolute type safety then you could simply create a std::set and store each flag inside there. It is not space efficient, but it is type safe and gives you the same ability as a bitflag int does.

如果您想要绝对类型安全,那么您可以简单地创建一个 std::set 并将每个标志存储在其中。它的空间效率不高,但它是类型安全的,并为您提供与 bitflag int 相同的功能。

C++0x note: Strongly typed enums

C++0x 注意:强类型枚举

In C++0x you can finally have type safe enum values....

在 C++0x 中,您终于可以拥有类型安全的枚举值....

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

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

回答by uliwitness

I find the currently accepted answer by eidolontoo dangerous. The compiler's optimizer might make assumptions about possible values in the enum and you might get garbage back with invalid values. And usually nobody wants to define all possible permutations in flags enums.

我发现eidolon目前接受的答案太危险了。编译器的优化器可能会对枚举中的可能值做出假设,并且您可能会得到无效值的垃圾。通常没有人想在标志枚举中定义所有可能的排列。

As Brian R. Bondy states below, if you're using C++11 (which everyone should, it's that good) you can now do this more easily with enum class:

正如下面的 Brian R. Bondy 所说,如果您使用的是 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. 

This ensures a stable size and value range by specifying a type for the enum, inhibits automatic downcasting of enums to ints etc. by using enum class, and uses constexprto ensure the code for the operators gets inlined and thus just as fast as regular numbers.

这通过为枚举指定类型来确保稳定的大小和值范围,通过使用禁止将枚举自动向下转换为整数等enum class,并用于constexpr确保运算符的代码被内联,因此与常规数字一样快。

For people stuck with pre-11 C++ dialects

对于坚持使用 11 之前的 C++ 方言的人

If I was stuck with a compiler that doesn't support C++11, I'd go with wrapping an int-type in a class that then permits only use of bitwise operators and the types from that enum to set its values:

如果我坚持使用不支持 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;
};

You can define this pretty much like a regular enum + typedef:

您可以像常规 enum + typedef 一样定义它:

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

typedef SafeEnum<enum TFlags_>  TFlags;

And usage is similar as well:

用法也类似:

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;

And you can also override the underlying type for binary-stable enums (like C++11's enum foo : type) using the second template parameter, i.e. typedef SafeEnum<enum TFlags_,uint8_t> TFlags;.

您还可以enum foo : type使用第二个模板参数(即typedef SafeEnum<enum TFlags_,uint8_t> TFlags;.

I marked the operator booloverride with C++11's explicitkeyword to prevent it from resulting in int conversions, as those could cause sets of flags to end up collapsed into 0 or 1 when writing them out. If you can't use C++11, leave that overload out and rewrite the first conditional in the example usage as (myFlags & EFlagTwo) == EFlagTwo.

operator bool用 C++11 的explicit关键字标记了覆盖,以防止它导致 int 转换,因为这可能导致标志集在写出时最终折叠为 0 或 1。如果您不能使用 C++11,请忽略该重载并将示例用法中的第一个条件重写为(myFlags & EFlagTwo) == EFlagTwo.

回答by soru

Easiest way to do this as shown here, using the standard library class bitset.

最简单的方法来做到这一点,如图在这里,使用标准库类的bitset

To emulate the C# feature in a type-safe way, you'd have to write a template wrapper around the bitset, replacing the int arguments with an enum given as a type parameter to the template. Something like:

要以类型安全的方式模拟 C# 功能,您必须围绕 bitset 编写模板包装器,将 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;

回答by Trevor

In my opinion none of the answers so far are ideal. To be ideal I would expect the solution:

在我看来,到目前为止,没有一个答案是理想的。为了理想,我希望解决方案:

  1. Support the ==,!=,=,&,&=,|,|=and ~operators in the conventional sense (i.e. a & b)
  2. Be type safe i.e. not permit non-enumerated values such as literals or integer types to be assigned (except for bitwise combinations of enumerated values) or allow an enum variable to be assigned to an integer type
  3. Permit expressions such as if (a & b)...
  4. Not require evil macros, implementation specific features or other hacks
  1. 支持==!==&&=||=~运营商在常规意义(即a & b
  2. 类型安全,即不允许分配非枚举值,例如文字或整数类型(枚举值的按位组合除外)或允许将枚举变量分配给整数类型
  3. 允许表达式,例如 if (a & b)...
  4. 不需要邪恶的宏、实现特定的功能或其他黑客

Most of the solutions thus far fall over on points 2 or 3. WebDancer's is the closes in my opinion but fails at point 3 and needs to be repeated for every enum.

到目前为止,大多数解决方案都在第 2 点或第 3 点上失败。在我看来,WebDancer 是关闭的,但在第 3 点失败,需要为每个枚举重复。

My proposed solution is a generalized version of WebDancer's that also addresses point 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;
}

This creates overloads of the necessary operators but uses SFINAE to limit them to enumerated types. Note that in the interests of brevity I haven't defined all of the operators but the only one that is any different is the &. The operators are currently global (i.e. apply to all enumerated types) but this could be reduced either by placing the overloads in a namespace (what I do), or by adding additional SFINAE conditions (perhaps using particular underlying types, or specially created type aliases). The underlying_type_tis a C++14 feature but it seems to be well supported and is easy to emulate for C++11 with a simple template<typename T> using underlying_type_t = underlying_type<T>::type;

这会创建必要运算符的重载,但使用 SFINAE 将它们限制为枚举类型。请注意,为了简洁起见,我没有定义所有的运算符,但唯一不同的是&. 运算符目前是全局的(即适用于所有枚举类型),但是可以通过将重载放在命名空间中(我所做的)或添加额外的 SFINAE 条件(可能使用特定的底层类型,或专门创建的类型别名)来减少这种情况)。这underlying_type_t是一个 C++14 特性,但它似乎得到了很好的支持,并且很容易用一个简单的方法模拟 C++11template<typename T> using underlying_type_t = underlying_type<T>::type;

回答by WebDancer

The C++ standard explicitly talks about this, see section "17.5.2.1.3 Bitmask types":

C++ 标准明确讨论了这一点,请参阅“17.5.2.1.3 位掩码类型”部分:

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

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

Given this "template" you get:

鉴于这个“模板”,你得到:

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

And similar for the other operators. Also note the "constexpr", it is needed if you want the compiler to be able to execute the operators compile time.

其他运营商也类似。另请注意“constexpr”,如果您希望编译器能够在编译时执行运算符,则需要它。

If you are using C++/CLI and want to able assign to enum members of ref classes you need to use tracking references instead:

如果您使用 C++/CLI 并希望能够分配给 ref 类的枚举成员,则需要改用跟踪引用:

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

NOTE: This sample is not complete, see section "17.5.2.1.3 Bitmask types" for a complete set of operators.

注意:此示例并不完整,请参阅“17.5.2.1.3 位掩码类型”部分以获取完整的运算符集。

回答by Omair

I found myself asking the same question and came up with a generic C++11 based solution, similar to soru's:

我发现自己在问同样的问题,并提出了一个基于 C++11 的通用解决方案,类似于 soru:

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

The interface can be improved to taste. Then it can be used like so:

界面可以改进以适应口味。然后它可以像这样使用:

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