与protobuf的枚举替换C++枚举

问题描述:

在我的代码库我有没有基础类型类似下面无范围的枚举:与protobuf的枚举替换C++枚举

enum EFoo { 
    EF_AAA  = 0, 
    EF_UNKNOWN = 1, 
    EF_BBB  = 2, 
    EF_MAX 
} 

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

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

这里是一个棘手的部分。随着时间的推移,可能会添加新的字段,如EF_CCC = 3,所以我不能像声明EF_MAX那样声明C++代码,因为它会破坏包含EFoo类型字段的序列化消息的二进制兼容性。并且EF_MAX用于整个代码库中的API,因为类型EFooEF_MAX变量的未知值从未序列化。然而,int类型的EFoo_ARRAYSIZE其语义含义正好是EF_MAX。所以我正在考虑用EFoo_ARRAYSIZE替换所有EF_MAX,但有一件事情让我困扰,它需要在某些地方做static_cast<EFoo>(EFoo_ARRAYSIZE)以避免编译器警告,根据标准它将被视为未定义的行为,这可能会导致讨厌的优化和错误。

而我的问题是,我该如何解决我的问题?或者,也许我错了,我的解决方案与static_cast<EFoo>(EFoo_ARRAYSIZE)全部替换EF_MAX是安全的吗?

万一它可能很重要,我在谈论C++ 11标准。

从协议缓冲器文档:

在反序列化,无法识别的枚举值将在消息中被保留,虽然这是如何表示当消息被反序列化是依赖于语言的。在支持指定符号范围之外的值的开放枚举类型(如C++和Go)的语言中,未知枚举值仅作为其基础整数表示形式存储。

在C++中的int可强制转换为枚举,如果你知道的INT表示有效枚举。这不是未定义的行为,它是该语言的一个可预测的特征。

请注意,由protobuf生成的枚举(至少今天)不是enum class类型,仅仅是enum

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

通过研究protoc的输出。

+0

“从协议缓冲区文档” - 链接? –

+0

真的吗?我需要为您的谷歌“协议缓冲区文件”?确定这里是:https://developers.google.com/protocol-buffers/docs/proto3#enum –

+0

我认为适当添加一个特定的引用尽可能直接引用,特别是如果它可以帮助读者知道规范的来源,以及未来的发展方向。特别是如果有人刚刚了解这个问题(毕竟这是很多人的想法)。考虑将其添加到您的帖子。 –

Proto3强制所有枚举通过添加两个“定点”值与INT_MIN和INT_MAX值枚举为32位:

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()语句,它必须有一个默认情况,否则你会得到一个警告。)

+0

出于好奇,在proto2中,出于安全原因,将电报上的未知枚举值视为未知字段? –

+1

@KostyaBazhanov有一个可能的安全性参数:收件人可能包含switch()语句,该语句涵盖每个预期的情况,但缺少默认情况。程序员不太可能想到在出现意外值的情况下会发生什么情况,并且可能最终导致可利用的错误。通过将该值视为未知字段,字段的getter现在返回该字段的默认值,这至少是程序员可能已经考虑和处理的情况。 OTOH,proto3迫使你有一个默认情况;也许这很好。 –