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中,因为EFoo和EF_MAX类型的变量从未被序列化。但是,存在类型为int的EFoo_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()语句,则该语句必须具有默认大小写,否则会得到警告。)
-
出于好奇,出于安全原因,在proto2中,网络上的未知枚举值被视为未知字段?
-
@KostyaBazhanov有一个可能的安全性参数:收件人可能包含switch()语句,该语句涵盖所有预期的情况,但缺少默认情况。程序员可能不曾考虑过在出现意外值的情况下会发生什么,并且很容易导致可利用的bug。通过将值视为未知字段,字段getter现在返回字段默认值,这至少是程序员可能已经考虑并处理的情况。 OTOH,proto3强制您使用默认大小写。也许那就足够了。
从协议缓冲区文档中:
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
并通过研究协议的输出。
-
"来自协议缓冲区文档"-链接?
-
真?我需要为您搜索"协议缓冲区文档"吗?好的,这里是:developers.google.com/protocol-buffers/docs/proto3#enum
-
我认为添加直接引号尽可能多地引用是适当的,尤其是当它可以帮助读者了解规范的来源以及将来的位置时。尤其是如果某人只是在学习有关问题(毕竟这就是很多事情的源头)。考虑将其添加到您的帖子中。
-
我知道将int枚举为枚举是安全的,我已经阅读了文档(否则我不知道EFoo_ARRAYSIZE)。将EFoo_ARRAYSIZE强制转换为EFoo是不安全的,因为根据其定义,EFoo_ARRAYSIZE不是有效的枚举,如果我真的不安全(例如在MSVS,GCC和Clang中),它就是我被卡住的地方在用protobuf枚举替换旧的C ++枚举之前,请更改API以避免使用EF_MAX。
-
如果您将枚举存储在protobuf消息中,则可以保证该枚举存储为int(请参见文档)。因此,只要在使用无效值之前检查其有效性,就可以有效地(当前)在该消息中存储无效值。但是,如果您不介意我说的话,那么TBH的设计就开始变得腥臭。
-
@RichardHodges,好吧,这段和平的代码大约在15年前编写的,它闻起来不一样了:)
-
首先重构,然后改变逻辑……黄金法则:-)
-
@RichardHodges因为Kostya显然在其他地方使用了实际的枚举类型(并且无论如何都将访问器强制转换为枚举类型),所以将枚举值存储为类内的整数这一事实并没有帮助。在proto3下,这很好,因为枚举类型被强制为32位,但是在proto2下,枚举类型将仅使用所需数量的位,这实际上可能导致Kostya出现不确定的行为。我已经添加了我自己的答案来解释所有这一切。
-
这次真是万分感谢。对那些哨兵价值观感到羞耻。在开关中使用default来隐藏警告会撤消警告的安全功能:/