在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。


当前回答

我认为目前接受的答案是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。

其他回答

我使用以下宏:

#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>

你可以像下面这样使用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类型转换,它是直接可用的。 同样,它也像枚举类一样是范围分离的

对于像我这样的懒人来说,下面是复制粘贴的模板解决方案:

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

我发现自己也在问同样的问题,并提出了一个基于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;

只有语法糖。没有额外的元数据。

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

整型上的标记操作符就可以了。