使用enum class实现标志位是否为未定义行为?C++20如何正确实现?
问题
我按照某篇Stack Overflow回答中的方式,通过宏重载运算符实现enum class的标志位操作,示例代码如下:
#define ENUMFLAGOPS(EnumName)\ [[nodiscard]] __forceinline EnumName operator|(EnumName lhs, EnumName rhs)\ {\ return static_cast<EnumName>(\ static_cast<std::underlying_type<EnumName>::type>(lhs) |\ static_cast<std::underlying_type<EnumName>::type>(rhs)\ );\ }// ...(其他运算符重载) enum class MyFlags : UINT { None = 0, FlagA = 1, FlagB = 2, FlagC = 4, }; ENUMFLAGOPS(MyFlags)
使用时的代码:
MyFlags Flags = MyFlags::FlagA | MyFlags::FlagB;
我担心这种写法会产生未定义行为,曾看到有说法称enum class变量的值若不属于定义的枚举值即为未定义行为,此处Flags的底层UINT值为3。请问这是否属于未定义行为?如果是,C++20中的正确实现方式是什么?
回答
1. 是否属于未定义行为?
这种写法不是未定义行为。
C++标准明确允许枚举类型(包括enum class)的对象存储其底层类型的任意有效值,哪怕该值没有对应的枚举器。只要你是通过合法的静态转换(将底层类型值转为枚举类型),或是通过枚举器做位运算后转回来的方式得到枚举对象,行为都是完全定义好的。
你听到的说法是错误的——只有当你用超出枚举底层类型值域范围的值去转换时,才会触发未定义行为(比如底层类型是uint8_t,却把256转成枚举类型)。这里的3是UINT的合法值,所以完全没问题。
2. C++20的正确实现方式
C++20引入了**std::bitmask概念**,同时新增了<bit>头文件和相关标准工具,你可以通过给枚举类型提供特化的std::is_bitmask_enum,让标准库自动支持标志位操作,无需自己写宏重载运算符。
示例代码如下:
#include <bit> #include <cstdint> enum class MyFlags : std::uint32_t { None = 0, FlagA = 1 << 0, FlagB = 1 << 1, FlagC = 1 << 2, }; // 特化std::is_bitmask_enum,告诉标准库这是一个标志位枚举 template<> struct std::is_bitmask_enum<MyFlags> : std::true_type {};
完成特化后,你可以直接使用|、&、^、~等位运算符,以及|=、&=、^=等复合赋值运算符:
MyFlags flags = MyFlags::FlagA | MyFlags::FlagB; flags &= MyFlags::FlagA; flags ^= MyFlags::FlagC;
另外,C++20还提供了std::bit_cast、std::has_single_bit等工具函数,能更安全地处理标志位枚举的底层值操作。
内容的提问来源于Stack Exchange,提问作者GLJeff
相关产品推荐
相关产品推荐

