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


当前回答

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

其他回答

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

复制粘贴的“邪恶”宏基于一些其他的答案在这个线程:

#include <type_traits>

/*
 * Macro to allow enum values to be combined and evaluated as flags.
 *  * Based on:
 *  - DEFINE_ENUM_FLAG_OPERATORS from <winnt.h>
 *  - https://stackoverflow.com/a/63031334/1624459
 */
#define MAKE_ENUM_FLAGS(TEnum)                                                      \
    inline TEnum operator~(TEnum a) {                                               \
        using TUnder = typename std::underlying_type_t<TEnum>;                      \
        return static_cast<TEnum>(~static_cast<TUnder>(a));                         \
    }                                                                               \
    inline TEnum operator|(TEnum a, TEnum b) {                                      \
        using TUnder = typename std::underlying_type_t<TEnum>;                      \
        return static_cast<TEnum>(static_cast<TUnder>(a) | static_cast<TUnder>(b)); \
    }                                                                               \
    inline TEnum operator&(TEnum a, TEnum b) {                                      \
        using TUnder = typename std::underlying_type_t<TEnum>;                      \
        return static_cast<TEnum>(static_cast<TUnder>(a) & static_cast<TUnder>(b)); \
    }                                                                               \
    inline TEnum operator^(TEnum a, TEnum b) {                                      \
        using TUnder = typename std::underlying_type_t<TEnum>;                      \
        return static_cast<TEnum>(static_cast<TUnder>(a) ^ static_cast<TUnder>(b)); \
    }                                                                               \
    inline TEnum& operator|=(TEnum& a, TEnum b) {                                   \
        using TUnder = typename std::underlying_type_t<TEnum>;                      \
        a = static_cast<TEnum>(static_cast<TUnder>(a) | static_cast<TUnder>(b));    \
        return a;                                                                   \
    }                                                                               \
    inline TEnum& operator&=(TEnum& a, TEnum b) {                                   \
        using TUnder = typename std::underlying_type_t<TEnum>;                      \
        a = static_cast<TEnum>(static_cast<TUnder>(a) & static_cast<TUnder>(b));    \
        return a;                                                                   \
    }                                                                               \
    inline TEnum& operator^=(TEnum& a, TEnum b) {                                   \
        using TUnder = typename std::underlying_type_t<TEnum>;                      \
        a = static_cast<TEnum>(static_cast<TUnder>(a) ^ static_cast<TUnder>(b));    \
        return a;                                                                   \
    }

使用

enum class Passability : std::uint8_t {
    Clear      = 0,
    GroundUnit = 1 << 1,
    FlyingUnit = 1 << 2,
    Building   = 1 << 3,
    Tree       = 1 << 4,
    Mountain   = 1 << 5,
    Blocked    = 1 << 6,
    Water      = 1 << 7,
    Coastline  = 1 << 8
};

MAKE_ENUM_FLAGS(Passability)

优势

仅在显式使用时应用于选定的枚举。 不使用非法的reinterpret_cast。 不需要指定底层类型。

笔记

如果使用c++ <14,将std::underlying_type_t<TEnum>替换为std::underlying_type<TEnum>::type。

最简单的方法如下所示,使用标准库类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;

我想详细说明Uliwitness的回答,为c++ 98修复他的代码,并使用Safe Bool习语,因为在c++ 11以下的c++版本中缺少std::underlying_type<>模板和显式关键字。

我还修改了它,使枚举值可以是连续的,而不需要任何显式的赋值,因此您可以有

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

海鹰是什么类型的?标志变量?

在标准c++中,枚举不是类型安全的。它们是有效的整数。

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

不需要像其他人建议的那样输入十六进制值。这没什么区别。

enum值默认为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的不同enum中放置无效标志。

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

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

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

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

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