0

给定不完整服务器的代码,例如:

enum class Command : uint32_t {
    LOGIN,
    MESSAGE,
    JOIN_CHANNEL,
    PART_CHANNEL,
    INVALID
};

我可以期望转换Command::LOGIN为整数总是会给出相同的值吗?

  • 跨编译器?
  • 跨编译器版本?
  • 如果我添加另一个枚举?
  • 如果我删除一个枚举?

转换Command::LOGIN看起来像这样:

uint32_t number = static_cast<uint32_t>(Command::LOGIN);

关于我在这里做什么的一些额外信息。该枚举通过将其转换为整数发送到服务器/客户端来馈送到线路上。我并不特别关心数字是多少,只要它始终保持不变即可。如果它不会保持不变,那么显然我将不得不通过通常的方式提供我自己的号码。

现在我偷偷地怀疑它会根据用于编译代码的编译器而改变,但我想确定。

奖励问题:编译器/语言如何确定要使用的数字Command::LOGIN

在提交这个问题之前,我已经注意到从say 3137527848 到 0 和返回的一些变化,因此依靠它不改变显然是无效的。我仍然很好奇这个数字是如何确定的,以及这个数字如何或为什么会发生变化。

4

3 回答 3

2

如果将显式整数值分配给枚举常量,则可以保证在转换为整数类型时始终具有相同的值。

只需执行以下操作:

enum class Command : uint32_t {
    LOGIN = 12,
    MESSAGE = 46,
    JOIN_CHANNEL = 5,
    PART_CHANNEL = 0,
    INVALID = 42
};

如果您没有显式指定任何值,则这些值是隐式设置的,从零开始,随着列表的每次向下移动而增加一。

引自草案n3485

[dcl.enum] 第 2 段

仅使用 enum 的 enum-key 声明的枚举类型是无作用域枚举,其枚举数是无作用域枚举数。enum-keys 枚举类和枚举结构在语义上是等价的;用其中之一声明的枚举类型是作用域枚举,它的枚举器是作用域枚举器。[...]枚举器列表中的标识符被声明为常量,并且可以出现在需要常量的任何地方。带有 = 的枚举器定义为关联的枚举器提供由常量表达式指示的值。如果第一个枚举器没有初始化器,则相应常量的值为零。没有初始化器的枚举器定义为枚举器提供了通过将前一个枚举器的值增加 1 获得的值

依赖这个的缺点是,如果列表顺序在未来发生某种变化,那么你的代码可能会默默地中断,所以我建议你明确一点。

于 2013-09-11T22:42:55.607 回答
2

来自 C++11 标准(或者更确切地说,n3485):

[dcl.enum]/2

如果第一个枚举器没有初始化器,则相应常量的值为零。没有初始化器枚举器定义为枚举器提供了通过将前一个枚举器的值增加一而获得的值。

此外,[expr.static.cast]/9

范围枚举类型的值可以显式转换为整数类型。如果原始值可以用指定的类型表示,则该值不变。

我认为很明显,枚举数的值可以用uint32_t;来表示。如果不是,[dcl.enum]/5 表示“如果枚举数的初始化值不能由底层类型表示,则程序格式错误。”

因此,只要您使用基础类型进行转换(显式或通过),只要您不在它们之前添加任何枚举器(在同一个枚举中)或更改它们的顺序std::underlying_type<Command>::type,这些枚举器的值就是固定的。

正如 Nicolas Louis Guillemo指出的那样,在传输值时要注意可能的不同字节顺序。

于 2013-09-11T22:58:22.860 回答
1

只要它是列表中的第一个枚举,Command::LOGIN 将始终为 0。请注意其余的枚举,因为根据计算机是使用大端还是小端,它们将具有不同的二进制表示。

于 2013-09-11T22:41:20.987 回答