【问题标题】:Zero initialization of enum with fixed value enumerators使用固定值枚举器对枚举进行零初始化
【发布时间】:2015-10-03 15:37:44
【问题描述】:

假设以下简单示例:

enum class Test { One = 1, Two = 2, Three = 3 };

int main()
{
    Test t{};
}

上面的代码似乎在 Clang、GCC 和 MSVC 上编译得很好。我担心的是,虽然用于表示各个枚举数的数值范围的 基础 整数类型完全能够将 0 表示为一个值,但 0 值并不映射到常量本身。正是这种差异令人困惑(整数类型范围与枚举的枚举数的范围)。从int 的角度来看,t 的值似乎是“定义明确的”,但从某种意义上说,它的定义并不明确,因为它作为标量没有映射到枚举常量之一。

我的搜索结果是this SO post,但最佳答案是非常技术性的,我不太明白。我在这里从一个稍微不同的角度提出这个问题,看看我是否能得到一个对我来说更有意义的答案。

具有固定值枚举器的枚举与没有它们的枚举之间是否存在不同的规则和/或保证?上面的代码对于带有未显式分配的枚举器的枚举非常有意义,这是令人困惑的固定值情况。

【问题讨论】:

  • 枚举类型的对象具有一系列有效值,这取决于枚举数的值。但是,并非该范围内的所有值都需要与枚举器相关联。考虑位标志,您将在其中 OR 一些枚举器并将结果转换回枚举类型。
  • @dyp 你有你提到的位标志场景的一个小的具体例子吗?
  • 嗯,想多了,我现在不会用这种方式写代码了。我在考虑enum bit_flags { FLAG_A = 1 << 0, FLAG_B = 1 << 1 }; void foo(bit_flags); foo( bit_flags(FLAG_A | FLAG_B) ); 有更好的解决方案,例如单独的bit_flag 和bit_set 类型,其中多个bit_flags 的OR'ing 产生bit_set。从类型安全的角度来看,foo(bit_flags) 可能仍然比一些foo(int) 更好。

标签: c++ c++11 enums


【解决方案1】:

枚举中的枚举器用于三个不同的目的:

  1. 他们为这种枚举类型的值定义名称。
  2. 如果基础类型不固定,它们会为枚举类型的对象塑造有效值的范围。
  3. 它们会影响枚举的基础类型,如果它不固定的话。

有效值的范围可以包括没有枚举器的值。这当然会令人困惑,例如当您编写一个涵盖所有枚举值的 switch 语句时。


我只是猜测,但枚举数和有效枚举值之间没有一对一映射的原因可能是与 C 兼容。在 C 中,枚举数的类型为 int,因此表示枚举数必须是枚举数所代表的int。因此,您不能将枚举器 A = 1 映射到值 0。 enums 可能是从整数 #defines 演变而来的。

现在,如果您从 int 转换为枚举类型,则不需要复杂的程序逻辑,例如 switch 将枚举数值 (ints) 映射到存储在枚举类型。事实上,C 要求枚举类型兼容到某些整数类型。这意味着枚举类型的 values 表示必须与兼容整数类型的表示相同。举个例子:

enum my_enum
{
    ENUMERATOR = 42
};

enum my_enum x = ENUMERATOR;

如果my_enum 与int 兼容,则x 中存储的值的表示形式必须与42 的表示形式相同。

兼容性要求将类型紧密地绑定在一起,据我所知,它们可以互换使用:

enum my_enum
{
    ENUMERATOR_A = 0,
    ENUMERATOR_B = ~0
};

void foo(int);

int main() {
    foo(0);
}

void foo(enum my_enum x) {}

clang 和 gcc 不认为这是错误,它们使用 int 作为 my_enum 的兼容类型。

因为枚举类型本质上是整数类型的 typedef,所以可能存在没有枚举数的值。


C++ 包含一些关于枚举和整数类型之间这种关系的提示,例如 [expr.static.cast] 和 [conv.integral] 谈到在整数和枚举类型之间转换时不改变值。但是,我不确定这本身是否有意义,因为我不确定如何观察这种变化,除非通过往返转换。

C++ 的枚举类型与其底层类型之间的严格别名规则也不例外。


请注意,在 C 中,我们可以利用这种关系来方便地使用位标志:

enum FLAGS
{
    FLAG_A = 1,
    FLAG_B = 2
};

void foo(enum FLAGS x);

int main() { foo(FLAG_A | FLAG_B); }

在 C++ 中,这是一个类型错误:FLAG_A | FLAG_B 是 int,并且不能隐式转换为 FLAGS。尽管可以编写显式转换,例如 foo( static_cast<FLAGS>(FLAG_A | FLAG_B) ),但在 C++ 中还有更方便的方法来编写位集。


问题的零初始化部分可能也非常相关:如果它可以通过单个memset 或静态数据来实现,则将它放在.bss 部分。因此,类型中所有位都为 0 的值非常有用。

必须补充一点,尽管 pointers-to-data-members 通常有一个空指针值,不是由所有位 0 表示,因此并非闻所未闻of 来破坏这个有用的属性。

【讨论】:

  • 在 The Design and Evolution of C++ (Bjarne Stroustrup) 以及 C99 Rationale 中有一些附加信息,但我认为它们只能作为进一步的引用/备份来源对于这个答案。仍然缺少的关键部分是 C++ 中奇怪的按位 有效范围 规则的基本原理。
【解决方案2】:

综上所述,保证仅指枚举指定的可能值范围(minValue,maxValue)。

没有固定值的枚举具有标准所保证的枚举值(§ 7.2/2)

一个枚举被保证在它的最小定义值和它最大的范围内保存任何值,并且未指定它如何处理范围之外的数字。所有的操作都是在它的底层类型上进行的

同样从 §5.2.9/10 开始,我们有关于如何将积分转换为枚举的规则:

整数或枚举类型的值可以显式转换为枚举类型。值不变,如果 原始值在枚举值(7.2)的范围内。 否则,结果值是未指定的(并且可能不在 那个范围)。

因此,将整数转换为超出范围的枚举值会给出未指定的值。这是一般规则,无论是否在声明中指定了枚举数值。但是范围是由底层类型给出的,它总是可以表示 xero,所以它不是未定义的(参见relevant answer)。

见Link to relevant question

【讨论】:

    【解决方案3】:

    具有固定值枚举器的枚举与没有固定值枚举器的枚举之间是否存在不同的规则和/或保证?

    没有。您应该将枚举视为类型安全(仅在 c++11 枚举类中保证类型安全)整数。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-04-01
      • 2013-06-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多