在c#中,通过[flags]属性将枚举视为标志,但在c++中实现这一点的最佳方法是什么?

例如,我想写:

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

seahawk.flags = CanFly | EatsFish | Endangered;

然而,我得到编译器错误关于int/enum转换。除了生硬的角色转换,还有更好的表达方式吗?最好,我不想依赖第三方库(如boost或Qt)的构造。

编辑:如答案中所示,我可以通过声明seahawk来避免编译器错误。标记为int。但是,我希望有某种机制来执行类型安全,这样就不能编写seahawk了。flags = HasMaximizeButton。


当前回答

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

其他回答

我认为目前接受的答案是eidolon太危险了。编译器的优化器可能会对枚举中可能的值进行假设,您可能会得到带有无效值的垃圾。通常没有人想在标记枚举中定义所有可能的排列。

正如Brian R. Bondy在下面所说的,如果你正在使用c++ 11(每个人都应该这样做,它很好),你现在可以用枚举类更容易地做到这一点:

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. 

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

对于那些受困于11年前c++方言的人来说

如果我被一个不支持c++ 11的编译器困住了,我会在一个类中包装一个int类型,然后只允许使用位操作符和该enum的类型来设置它的值:

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;

你也可以使用第二个模板参数覆盖二进制稳定enum的底层类型(如c++ 11的enum foo: type),即typedef SafeEnum<enum TFlags_,uint8_t> TFlags;。

我用c++ 11的显式关键字标记了bool override操作符,以防止它导致int转换,因为这可能导致一组标志在写入时被折叠成0或1。如果你不能使用c++ 11,那就把这个重载去掉,并重写示例用法中的第一个条件为(myFlags & EFlagTwo) == EFlagTwo。

注意(也有点离题):另一种制作唯一标志的方法可以使用位移位。我自己觉得这更容易理解。

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

它可以保存不超过int的值,也就是说,大多数情况下,32个标志清楚地反映在移位量中。

最简单的方法如下所示,使用标准库类bitset。

为了以一种类型安全的方式模拟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;

你可以像下面这样使用struct:

struct UiFlags2 {
    static const int
    FULLSCREEN = 0x00000004,               //api 16
    HIDE_NAVIGATION = 0x00000002,          //api 14
    LAYOUT_HIDE_NAVIGATION = 0x00000200,   //api 16
    LAYOUT_FULLSCREEN = 0x00000400,        //api 16
    LAYOUT_STABLE = 0x00000100,            //api 16
    IMMERSIVE_STICKY = 0x00001000;         //api 19
};

并像这样使用:

int flags = UiFlags2::FULLSCREEN | UiFlags2::HIDE_NAVIGATION;

所以你不需要int类型转换,它是直接可用的。 同样,它也像枚举类一样是范围分离的

也许像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.