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


187

通过将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]属性仅适用于C ++ CLI中的.Net Framework,本机C ++不支持此类属性。
Zoltan Tirinda

Answers:


249

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

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

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


42
^这个。唯一的问题是如何自动化/模板化运算符定义,因此您不必在每次添加新枚举时都不断对其进行定义。
eodabash 2011年

10
而且,即使int值不对应于任何枚举的标识符,从任意int强制转换为枚举类型也有效吗?
Ingo Schalk-Schupp 2013年

8
这是完全废话。AnimalFlags表达式代表哪个成员HasClaws | CanFly?这不是什么enums为的。使用整数和常量。
Lightness Races in Orbit

25
@LightnessRacesinOrbit:不正确。枚举类型的域是其基础类型的域-仅给某些特定的名称了。并回答您的问题:“ (HasClaws | CanFly)” 成员。
Xeo

5
@MarcusJ:将值限制为2的幂允许您将枚举用作位标志。因此,如果您得到3,则将同时知道HasClaws(= 1)和CanFly(= 2)。相反,如果你只通过4直通分配值1,你会得到一个3,这可能是一个单一的EatsFish,或重新组合HasClawsCanFly。如果枚举仅表示互斥状态,则连续的值就可以了,但是标志的组合需要将这些值设置为位互斥的。
Christian Severin

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,)并在}之后添加冒号,以使代码易于复制和粘贴?谢谢
Katu 2014年

4
不提十六进制?亵渎!
法拉普2014年

1
@Jamie,红衣主教总是以1开头,只有普通词可以以0或1开头,这取决于与谁交谈。
Michael

2
@Michael,是的!在一个枚举中,通常为BLAH_NONE保留0。:-)感谢您破坏记忆!
杰米

1
@Katu•标准允许在最终枚举中使用多余的逗号。我不喜欢它,但是我已经知道Stroustrup会告诉我什么……“您不喜欢它吗?随时创建自己的语言。我做到了。”
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魔术。
2014年

@Rai:您总是可以将其放在命名空间中,using并在适当的地方放置它,就像rel_ops
Yakov Galka '16

1
@ybungalobill,但是在使用范围内应用于任何类型的操作仍然会遇到相同的问题,这大概会与枚举匹配?我认为特质是最有必要的。
Rai 2016年

19
不要使用此代码。它为任何错误操作的类别打开了大门。另外,代码使用的是旧样式转换,不会通过GCC严格的编译shitalshah.com/p/…传递
Shital Shah

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;

43

seahawk.flags变量是什么类型?

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

AnimalFlags不应为变量的类型。您的变量应为int,错误将消失。

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

枚举值默认为int类型。因此,您当然可以按位或将它们组合在一起并将它们存储在一个int中。

枚举类型是int的受限子集,其值是其枚举值之一。因此,当您在该范围之外创建一些新值时,如果不将其强制转换为枚举类型的变量,就无法进行赋值。

您也可以根据需要更改枚举值类型,但是这个问题毫无意义。

编辑:张贴者说他们担心类型安全,并且他们不希望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并将每个标志存储在其中。它不是节省空间的,但是它是类型安全的,并且具有与bitflag int相同的功能。

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

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

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

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

4
枚举值不是整数,但是很容易转换为整数。的类型HasClaws | CanFly是某种整数类型,但是的类型HasClawsAnimalFlags,而不是整数类型。
卡鲁2012年

嗯,但是如果我们将枚举的正确范围定义为不仅是单个标志值,而且是它们的按位组合,该怎么办?然后eidolon的答案是正确的,并认为只有正确的标志枚举的组合才能作为该类型传递。
斯科特,

3
@Scott:值得注意的是,C ++标准以这种方式定义了枚举实例的有效值范围。“对于emin是最小的枚举数且emax是最大的枚举,该枚举的值是bmin到bmax范围内的值,定义如下:设K为2的补码表示,0为1的补码表示。 bmax是大于或等于max(|emin| − K, |emax|)并等于的最小值(1u<<M) - 1,其中b M是非负整数。”
Ben Voigt

对于那些(像我一样)只需要实用的东西,它允许按枚举方式枚举枚举值,并且在模板和类型转换方面看起来并不难看,这是一个很好的解决方案。只需将变量定义为type即可int
埃里克·索科洛斯基

还要注意,在C ++中,经常enum在技术上不默认为int作为其底层类型(预C ++ 11(这个),或交-C ++中没有指定的基本类型时11),虽然enum class 确实。取而代之的是,基础类型默认使用足以代表所有枚举数的默认值,唯一真正的硬法则是,它仅比int显式需要的要大。基本上,底层类型被指定为“(无论如何),但是除非枚举器对于“ 太大,否则可能 int是这样int
贾斯汀时间-恢复莫妮卡

26

我找到了eidolon当前接受的答案太危险了。编译器的优化器可能会对枚举中的可能值进行假设,并且您可能会使用无效值来回收垃圾。通常,没有人希望在标志枚举中定义所有可能的排列。

正如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. 

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

适用于11年前C ++方言的人

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

您可以像常规枚举+ 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;

您还可以enum foo : type使用第二个模板参数(即)覆盖二进制稳定枚举的基础类型(如C ++ 11的)typedef SafeEnum<enum TFlags_,uint8_t> TFlags;

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


注意,我建议在开始时定义的示例运算符使用std::underlying_type而不是对特定类型进行硬编码,或者建议提供基础类型并将其用作类型别名,而不是直接使用。这样,对基础类型的更改将自动传播,而不必手动进行。
贾斯汀时间-恢复莫妮卡

17

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

要以类型安全的方式模拟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/…–
Shital 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;


尽管您提出的解决方案效果很好,但它也为不被视为标志的枚举引入了这种模式。这可能是使用诸如Microsoft的DEFINE_ENUM_FLAG_OPERATORS之类的(邪恶的)宏的原因。
WebDancer

@WebDancer,您当然是正确的,但是我已经在回答中说了这一点。我还提出了两种解决问题的方法-将其放在名称空间中或使用限制性更强的SFINAE条件。
Trevor

我的观点是,除非您创建一个非常狭窄的名称空间(例如,名称空间AllMyFlagEnums)或具有SFINAE条件,该条件以某种方式仅选择了几个确切的枚举,但这些代码在我的脑海中却被打破了。我没有冒这个险,而是复制并粘贴了“文本模板”,在这里我只替换了枚举名称,有时还替换了“邪恶的”宏。我希望有更好的方法。
WebDancer

首先,仅当您在代码中的其他地方需要执行打算停止的事情之一(例如,从另一个枚举中分配文字,整数或元素)时,这才会引起问题。否则,修改后的枚举的行为类似于常规枚举,例如,元素不一定需要为2的幂,并且赋值,比较和按位运算都可以正常进行。如果确实必须分配文字或混合枚举,则仍可以显式进行强制转换,并具有额外的优点,即可以使意图更清晰。因此,有可能不需要缩小范围。
Trevor

其次,即使您确实需要缩小范围,名称空间也不必狭窄-尽管这取决于您在做什么。如果您正在使用库,那么也许您已经有依赖于名称空间中枚举的代码,那么枚举代码仅位于相同的名称空间中。如果您需要类的枚举行为(也许您想将枚举用作类的方法参数或成员变量),则将枚举代码放在类中以达到相同的效果。最重要的是,尽管可以,但您不需要仅在枚举周围包装名称空间。
Trevor

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的通用解决方案,类似于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);
    }   
};

界面可以改善口味。然后可以这样使用:

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

2
请查看以下内容以获得更好更完整的代码: codereview.stackexchange.com/questions/96146/…–
Shital Shah

5
除了我对numeric_limits的使用外,代码几乎相同。我想这是拥有类型安全的枚举类的常见方法。我认为使用numeric_limits比在每个枚举的末尾放置SENTINEL更好。
Omair

1
这是一个巨大的问题
Lightness Races in Orbit

(可能...)
轻轨赛将于

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习惯用法,因为在C ++ 11以下的C ++版本中缺少std::underlying_type<>模板和explicit关键字。

我还对其进行了修改,以便枚举值可以是连续的,而无需任何显式分配,因此您可以

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 :(";
    }
}

我们可以看到生活很棒,我们拥有离散的值,并且对&和|有很好的理解。令我们满意的是,内容仍然具有含义。只要我在Win10 x64上继续使用带有Update 3的Microsoft VC ++编译器并且不碰我的编译器标志,一切对我来说都是一致的和可预测的...

即使一切都很好...我们现在对标志的含义也有一定的了解,因为它与可怕的现实世界中的位域结合在一起,在这里,您的程序可能要负责多个单独的离散任务仍然不小心(很容易)将不同联合的两个flag字段粉碎在一起(例如,AnimalProperties和ObjectProperties,因为它们都是ints),混合了您所有的位,这是一个可怕的错误,以至于无法找到...我怎么知道这篇文章中的许多人都不经常使用位掩码,因为构建位掩码很容易,而维护起来却很困难。

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 :(
}

因此,然后将联合声明设为私有,以防止直接访问“标志”,并且必须添加getter / setter和运算符重载,然后为所有这些创建宏,然后您基本上就回到了尝试开始时的位置用枚举来做到这一点。

不幸的是,如果您希望代码具有可移植性,那么我认为没有任何方法可以保证A)保证位布局或B)在编译时确定位布局(以便您可以跟踪它,并且至少可以纠正跨位的更改)版本/平台等) 具有位字段的结构中的偏移量

在运行时,您可以通过设置字段和对标志进行异或操作来查看哪些位确实发生了变化,这对我来说听起来很糟糕,尽管这些经文具有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”,但是概念是相同的。
yoyo

(对不起,“ constexpr int”)
yoyo

3

当前没有对枚举标志的语言支持,如果Meta类将成为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,依此类推。
yau

1

如上(Kai)或执行以下操作。真正的枚举是“枚举”,您想要做的就是设置一个集合,因此您应该真正使用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

可能类似于Objective-C的NS_OPTIONS。

#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
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.