Language Guide (proto3) | proto3 语言指南(九)Oneof结构
Oneof - Oneof结构
如果消息包含多个字段,并且最多只能同时设置一个字段,则可以使用oneof
功能强制执行此行为并节省内存。
oneof
字段与常规字段类似,但oneof
共享内存中的所有字段除外,并且oneof
最多只能同时设置一个字段。设置oneof
的任何成员将自动清除所有其他成员。您可以使用特殊的case()
或WhichOneof()
方法检查oneof
中设置了哪个值(如果有被设置),具体取决于您选择的语言。
使用oneof
结构
要在.proto
中定义oneof
结构,请使用oneof
关键字后跟oneof名称
。请看示例test_oneof
:
message SampleMessage {
oneof test_oneof {
string name = 4;
SubMessage sub_message = 9;
}
}
然后将oneof
字段添加到oneof
定义。你可以添加任意类型的字段,除了map
和repeated
类型的字段。
在生成的代码中,oneof
字段与常规字段一样具有getter
(访问器)和setter
(设置器)。您还可以使用一种特殊的方法来检查oneof
结构中设置了哪个值(如果有的话)。您可以在相关的API参考中找到有关所选语言的oneof API
的更多信息。
oneof
的功能
- 设置oneof字段将自动清除oneof的所有其他成员。因此,如果您设置了几个字段中的一个,那么只有最后设置的字段有效。
SampleMessage message;
message.set_name("name");
CHECK(message.has_name());
message.mutable_sub_message(); // Will clear name field.
CHECK(!message.has_name());
- 如果解析器在
wire
上遇到同一个oneof
的多个成员,则在解析的消息中只使用看到的最后一个成员。 oneof
结构不能使用repeated
修饰符。- 反射API适用于
oneof
字段。 - 如果将
oneof
字段设置为默认值(例如将int32类型的oneof
字段设置为0),则将设置该oneof
字段的case
,并且该值将在wire
上序列化。 - 如果使用
C++
,请确保代码不会导致内存崩溃。下面的示例代码将崩溃,因为在调用set_name()
方法时已经删除了sub_message
。
SampleMessage message;
SubMessage* sub_message = message.mutable_sub_message();
message.set_name("name"); // Will delete sub_message
sub_message->set_... // Crashes here
- 同样在
C++
中,如果你使用Swap()
方法交换两条使用了oneof
结构的消息,每条消息都将以另外一条消息的oneof case
结束:在下面的示例中,msg1
将有一个sub_message
字段,msg2
将有一个name
字段。
SampleMessage msg1;
msg1.set_name("name");
SampleMessage msg2;
msg2.mutable_sub_message();
msg1.swap(&msg2);
CHECK(msg1.has_sub_message());
CHECK(msg2.has_name());
向后(下)兼容问题
添加或删除oneof
字段时要小心。如果检查oneof
字段的值返回None/NOT_SET
,则可能意味着此oneof
尚未设置或已将其设置为oneof
的其他版本中的字段。因为无法知道wire
的未知字段是否是其中一个字段的成员,所以无法区分两者之间的区别。
标签重用问题
- 将字段移入或移出
oneof
:消息序列化和解析后,可能会丢失一些信息(某些字段将被清除)。但是,您可以安全地将single
字段移动到新的oneof
字段中,并且如果已知只设置了一个字段,则可以移动多个字段。 - 删除
oneof
字段并将其加回:这可能会在序列化和解析消息后清除当前设置的oneof
字段。 - 拆分或合并
oneof
字段:这与移动常规字段有类似的问题