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


当前回答

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

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

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

其他回答

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

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 range,则根据需要修改。

下面是我的解决方案,不需要任何一堆重载或强制转换:

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初始化列表和枚举类。

你可以像下面这样使用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++ 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对操作数进行完美的转发。

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

来自摘要:

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