提问人:GLJeff 提问时间:1/2/2023 最后编辑:ElliottGLJeff 更新时间:1/3/2023 访问量:215
使用枚举类来标记未定义的行为吗?
Is using enum class for flags undefined behavior?
问:
我一直在使用重载运算符,如这里的第二个答案所示:如何使用 C++11 枚举类作为标志......例:
#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)\
);\
}...(other operator overloads)
enum class MyFlags : UINT //duplicated in JS
{
None = 0,
FlagA = 1,
FlagB = 2,
FlagC = 4,
};
ENUMFLAGOPS(MyFlags)
...
MyFlags Flags = MyFlags::FlagA | MyFlags::FlagB;
我开始担心这可能会产生未定义的行为。我看到它提到,仅仅有一个不等于定义的枚举值之一的枚举类变量是未定义的行为。在本例中,Flags 的基础 UINT 值为 3。这是未定义的行为吗?如果是这样,在 c++20 中执行此操作的正确方法是什么?
答:
一种误解是枚举类型只有它声明的值。
枚举具有基础类型的所有值。只是在枚举中,其中一些值具有名称。通过 ing 获取没有名称的值是完全可以的,或者在经典枚举的情况下,通过运算 () 或简单赋值来获取。static_cast
|
您的代码非常好(除了可能引起一些对宏使用的关注)。
9.7.1 枚举声明 [dcl.enum]
- 对于基础类型为固定的枚举,枚举的值是基础类型的值。
对于基础类型不固定的枚举(即 is missing)标准说的基本上是一样的,但以一种更复杂的方式:枚举与基础类型具有相同的值,但关于基础类型是什么有更多的规则。: std::uint32_t
这超出了您的问题范围,但您可以在没有任何宏的情况下定义运算符,我强烈推荐它:
template <class E>
concept EnumFlag = std::is_enum_v<E> && requires() { {E::FlagTag}; };
template <EnumFlag E>
[[nodiscard]] constexpr E operator|(E lhs, E rhs)
{
return static_cast<E>(std::to_underlying(lhs) | std::to_underlying(rhs));
}
enum class MyFlags : std::uint32_t
{
None = 0x00,
FlagA = 0x01,
FlagB = 0x02,
FlagC = 0x04,
FlagTag = 0x00,
};
是的,您可以有多个具有相同值的“名称”(枚举器)。因为我们不使用价值,所以它有什么价值并不重要。FlagTag
要标记要为其定义运算符的枚举,可以使用上面示例中的标记,也可以使用类型特征:
template <class E>
struct is_enum_flag : std::false_type {};
template <>
struct is_enum_flag<MyFlags> : std::true_type {};
template <class E>
concept EnumFlag = is_enum_flag<E>::value;
评论
operator|
EnumFlag
to_underlying
static_cast
C 和 C++ 标准都没有区分那些在不了解类型位模式和相关陷阱表示(或缺乏)的情况下单独检查将是未定义行为的行为,以及那些“未定义性”胜过人们对此类事物的任何了解的行为。C99标准改变了何时为负数的处理方式,也许最能说明这一理念;我 C++ 标准可能有一些更好的例子,但我对它并不熟悉。x<<1
x
如果一个平台有一个比普通指令更快的 8 位存储指令,除了尝试存储 1100 0000 的位模式会导致 CPU 过热和熔化,我认为 C++ 标准中的任何内容都不会禁止 C++ 实现提供使用该存储指令的扩展类型, 并使用这样的类型来表示其类型未指定,其值包括 -63 和 -62,但不包括 -64。如果不能确定代码不会在这样的平台上运行,就无法知道尝试分别执行 when 和 hold -63 和 -62 是否会让 CPU 着火。因此,就标准而言,后一种结构将是未定义的行为。int_least7_t
enum
myEnum1 = (myEnumType)((int)myEnum2 & (int)myEnum3);
myEnum2
myEnum3
C 和 C++ 标准都陷入了两难境地,有些人认为标准不应该添加新的文本来说明几十年来一直处理的结构应该继续存在,而另一些人则认为缺乏任何此类授权是邀请将长期实践抛在窗外。要知道是否应该期望任何特定的结构起作用,唯一的方法是知道负责目标实现的人是否尊重先例或将其视为“优化”的障碍。
评论