关于c ++ 11:用protobuf枚举替换C ++枚举

Replace C++ enumeration with protobuf enumeration

在我的代码库中,我具有无范围的枚举,没有下面的类型:

1
2
3
4
5
6
enum EFoo {
    EF_AAA     = 0,
    EF_UNKNOWN = 1,
    EF_BBB     = 2,
    EF_MAX
}

我想使其成为一个protobuf枚举,以便可以将它直接作为枚举而不是某些int*字段在其他protobuf消息中重用。所以我想象.proto文件中的enum声明将如下所示:

1
2
3
4
5
enum EFoo {
     EF_AAA    = 0;
     EF_UKNOWN = 1;
     EF_BBB    = 2;
}

这是一个棘手的部分。随着时间的流逝,可能会添加诸如EF_CCC = 3之类的新字段,因此我无法像在C ++代码中那样声明EF_MAX,因为它会破坏与包含EFoo类型的字段的序列化消息的二进制兼容性。而且EF_MAX在整个代码库中都用在API中,因为EFooEF_MAX类型的变量从未被序列化。但是,存在类型为intEFoo_ARRAYSIZE,其语义确切为EF_MAX。因此,我正在考虑将所有EF_MAX替换为EFoo_ARRAYSIZE,但是有一件事困扰我,它将需要在某些地方执行static_cast(EFoo_ARRAYSIZE)以避免编译器警告,并且根据标准,它将被考虑作为未定义的行为,可能会导致讨厌的优化和错误。

我的问题是,我该如何解决我的问题?还是我错了,用static_cast(EFoo_ARRAYSIZE)替换所有EF_MAX的解决方案是安全的?

万一这很重要,我在说的是C ++ 11标准。


Proto3通过向枚举中添加两个具有INT_MIN和INT_MAX值的"前哨"值来强制所有枚举为32位:

1
2
3
4
5
6
enum Foo {
  Foo_FOO = 0,
  Foo_BAR = 1,
  Foo_Foo_INT_MIN_SENTINEL_DO_NOT_USE_ = ::google::protobuf::kint32min,
  Foo_Foo_INT_MAX_SENTINEL_DO_NOT_USE_ = ::google::protobuf::kint32max
};

这样的效果是,枚举类型Foo将始终使用32位整数(或更大)作为其表??示形式,因此您可以使用static_cast s将任何32位值存储到其中。

请注意,proto2并未执行此操作,因此将ARRAYSIZE常量强制转换为proto2中的枚举类型并不安全。特别是在proto2中,如果您有一个最大值为127的枚举,则可以使用8位带符号类型表示该枚举。 ARRAYSIZE值将为128,该值不在该类型的范围内-可能被视为-128。更糟糕的是,编译器可能会应用优化,从而导致完全无法预测的行为。

这就是为什么proto2将导线上的未知枚举值当作未知字段来对待,就像根本不存在该字段值一样。这种行为使很多人感到困惑,因此proto3切换为将枚举强制为32位。 (请注意,proto3的方法意味着,如果您在枚举值上包含switch()语句,则该语句必须具有默认大小写,否则会得到警告。)


从协议缓冲区文档中:

During deserialization, unrecognized enum values will be preserved in the message, though how this is represented when the message is deserialized is language-dependent. In languages that support open enum types with values outside the range of specified symbols, such as C++ and Go, the unknown enum value is simply stored as its underlying integer representation.

在c ++中,如果您知道int表示有效的枚举,则可以将int强制转换为枚举。这不是不确定的行为,它是语言的可预测的功能。

请注意,protobuf生成的枚举(至少在今天为止)不是enum class类型,而只是enum类型。

从这里:https://developers.google.com/protocol-buffers/docs/proto3#enum

并通过研究协议的输出。