如何使用 C++11 枚举类作为标志

问题描述 投票:0回答:8

说我有这样一堂课:

enum class Flags : char
{
    FLAG_1 = 1;
    FLAG_2 = 2;
    FLAG_3 = 4;
    FLAG_4 = 8;
};

现在我可以有一个具有类型标志的变量并分配一个值

7
吗?我可以这样做吗:

Flags f = Flags::FLAG_1 | Flags::FLAG_2 | Flags::FLAG_3;

Flags f = 7;

出现这个问题是因为在枚举中我没有定义

7
的值。

c++ c++11 enums
8个回答
29
投票

您需要编写自己的重载

operator|
(大概还有
operator&
等)。

Flags operator|(Flags lhs, Flags rhs) 
{
    return static_cast<Flags>(static_cast<char>(lhs) | static_cast<char>(rhs));
}

只要该值在枚举值的范围内(否则为 UB;[expr.static.cast]/p10),整数到枚举类型(有作用域或无作用域)的转换就是明确定义的。对于具有固定基础类型的枚举(这包括所有作用域枚举;[dcl.enum]/p5),枚举值的范围与基础类型的值范围相同([dcl.enum]/p8)。如果底层类型不固定,规则会更棘手 - 所以不要这样做:)


17
投票

使用

std::underlying_type
而不是硬编码
char
类型可能会更好。

Flags operator|(Flags lhs, Flags rhs) {
    return static_cast<Flags>(
        static_cast<std::underlying_type<Flags>::type>(lhs) |
        static_cast<std::underlying_type<Flags>::type>(rhs)
    );
}

现在,您可以更改枚举的基础类型,而无需在每个按位运算符重载中更新该类型。


5
投票

它应该处理任何枚举类型。我不确定它没有任何副作用并且是完全有效的 C++ 代码。如果有任何问题请告诉我。

template<class T, std::enable_if_t<std::is_enum_v<T>, int> = 0>
constexpr T operator|(T lhs, T rhs) 
{
    return static_cast<T>(
        static_cast<std::underlying_type<T>::type>(lhs) | 
        static_cast<std::underlying_type<T>::type>(rhs));
}

3
投票

请不要这样做。如果您需要这样做,

enum class
可能不是您所需要的。

@T.C.向您展示了如何做到这一点,只要您指定底层类型,但是您会遇到程序执行不应该执行的操作的地方。

一个示例是,您使用

switch
并为每个
defined
枚举值使用 case

例如

enum class my_enum: unsigned int{
    first = 1,
    second = 2,
    third = 4,
    fourth = 8
};

int main(){
    auto e = static_cast<my_enum>(static_cast<unsigned int>(my_enum::first) | static_cast<unsigned int>(my_enum::second));

    switch(e){
        case my_enum::first:
        case my_enum::second:
        case my_enum::third:
        case my_enum::fourth:
            return 0;
    }

    std::cout << "Oh, no! You reached a part of the program you weren't meant to!\n";
    return 1;
}

将输出:

Oh, no! You reached a part of the program you weren't meant to!

然后返回错误代码

1

这也是为什么你应该总是有一个

default
案例的例子,当然,但这不是我的观点。

当然,你可能会争辩说,只要

enum class
的用户除了传递给函数之外从不直接使用该值;这将是限制位集值的好方法。但我总是有点太值得信赖,并发现
std::uint[n]_t
和一些
constexpr
变量是最好的方法(如果用户设置了无效位,它什么也不做)。

你正在做的事情并不真正适合

enum class
,因为它违背了范围化枚举的目的。如果将其设置为未定义的值,则无法再枚举这些值。


2
投票

我意识到这个问题有点老了,但我会写出我用来做到这一点的方法。

(如果有的话,如果我将来再次谷歌这个,我会把它记录下来以便再次找到。)

我个人喜欢这种方法,因为智能感知(至少是它的 VSCode 版本...我在 Linux 上没有 Visual Studio...)会自动识别您正在做的事情并为您提供有用的提示。此外,它避免使用宏,因此编译器如果不高兴可以警告您。最后,如果没有注释,代码就不多了。您没有创建一个类并设置一堆重载或任何东西,但您仍然可以获得作用域枚举的好处,以便您可以为另一个枚举重用标志名称/值。不管怎样,还是看例子吧。

namespace FlagsNS
{
    /* This is an old/classic style enum so put it in a
    namespace so that the names don't clash
    (ie: you can define another enum with the values of 
    Flag_1 or Flag_2, etc... without it blowing up)
    */
    enum Flags
    {
        Flag_1 = 1 << 0, //Same as 1
        Flag_2 = 1 << 1, //Same as 2
        Flag_3 = 1 << 2, //Same as 4
        Flag_4 = 1 << 3 //Same as 8
    };
}
/* This is telling the compiler you want a new "type" called Flags
but it is actually FlagsNS::Flags. This is sort of like using the
#define macro, except it doesn't use the preprocessor so the
compiler can give you warnings and errors.
*/
using Flags = FlagsNS::Flags;

//Later in code.... so int main() for example
int main()
{
    //If you don't mind c-style casting
    Flags flag = (Flags)(Flags::FLAG_1 | Flags::FLAG_2 | Flags::FLAG_3);
    //Or if you want to use c++ style casting
    Flags flag = static_cast<Flags>(Flags::FLAG_1 | Flags::FLAG_2 | Flags::FLAG_3);

    //Check to see if flag has the FLAG_1 flag set.
    if (flag & Flags::FLAG_1)
    {
        //This code works
    }
}

0
投票

有问题的代码无法编译。但你可以做这样的事情,

enum class Flags : char
{
    FLAG_1 = 1,
    FLAG_2 = 2,
    FLAG_3 = 4,
    FLAG_4 = 8,
};

int main() {
    Flags f = static_cast<Flags>(7);
    Flags f1 = static_cast<Flags>( static_cast<char>(Flags::FLAG_1) | static_cast<char>(Flags::FLAG_2) | static_cast<char>(Flags::FLAG_3) );
    return 0;
}

而且它有效


0
投票

不要将

enum class
用于按位或枚举

仅将

enum class
用于不同状态且不能按位组合的枚举

运算符重载你的

enum class
确实是代码膨胀,当
enum
默认提供时,这样做是不明智的

如果您想避免使用

enum
标志污染全局命名空间,
namespace
枚举:

namespace ColorComponent {
  enum Enum {
    None   = 0,
    Red    = 1 << 0,
    Green  = 1 << 1,
    Blue   = 1 << 2,
  };
};

您可以使用它

enum
,例如:

ColorComponent::Enum e = (ColorComponent::Enum)(ColorComponent::Red | ColorComponent::Blue);

-1
投票

此时,定义自己的类来处理这个问题可能是有意义的。

 /** Warning: Untested code **/
 struct Flag {

     static Flag Flag_1;
     static Flag Flag_2;
     static Flag Flag_3;
     static Flag Flag_4;

     Flag operator = (Flag);
 private:
     char const value;
 };

 Flag operator | (Flag, Flag); 
© www.soinside.com 2019 - 2024. All rights reserved.