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


当前回答

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

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

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

其他回答

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

在标准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

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

如上(启)或做以下。实际上枚举是“枚举”,你想做的是有一个集合,因此你应该使用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);
}

如果你的编译器还不支持强类型枚举,你可以参考c++源代码中的下面这篇文章:

来自摘要:

本文提出了一种解决比特操作约束问题的方法 只允许安全和合法的操作,并将所有无效的位操作转换为 编译时错误。最重要的是,位操作的语法保持不变, 使用比特的代码不需要修改,除非可能修改 修复尚未检测到的错误。

c++ 20类型安全Enum操作符

博士TL;

template<typename T>
requires std::is_enum_v<T> and
         requires (std::underlying_type_t<T> x) {
             { x | x } -> std::same_as<std::underlying_type_t<T>>;
             T(x);
         }
T operator|(T left, T right)
{
    using U = std::underlying_type_t<T>;
    return T( U(left) | U(right) );
}

template<typename T>
requires std::is_enum_v<T> and
         requires (std::underlying_type_t<T> x) {
             { x | x } -> std::same_as<std::underlying_type_t<T>>;
             T(x);
         }
T operator&(T left, T right)
{
    using U = std::underlying_type_t<T>;
    return T( U(left) & U(right) );
}

template<typename T>
requires std::is_enum_v<T> and requires (T x) { { x | x } -> std::same_as<T>; }
T & operator|=(T &left, T right)
{
    return left = left | right;
}

template<typename T>
requires std::is_enum_v<T> and requires (T x) { { x & x } -> std::same_as<T>; }
T & operator&=(T &left, T right)
{
    return left = left & right;
}

基本原理

使用类型特征std::is_enum,我们可以测试一些类型T是否为枚举类型。 这包括无作用域和有作用域的枚举(即enum和enum类)。 使用类型trait std::underlying_type,我们可以得到枚举的底层类型。 使用c++ 20的概念和约束,很容易为按位操作提供重载。

有作用域和无作用域

如果操作只应该重载有作用域或无作用域的枚举,std::is_scoped_enum可以用于相应地扩展模板约束。

c++ 23

在c++ 23中,我们使用std::to_underlying来更容易地将枚举值转换为其底层类型。

移动语义和完善转发

如果你遇到了一种奇怪的情况,即你的底层类型对于copy和move有不同的语义,或者它不提供复制c'tor,那么你应该使用std::forward对操作数进行完美的转发。