Proto3:C++代码生成指南
本章节实际上是介绍Protocol Buffer编译器从给定的protocol定义中生成的C++代码。所有proto2和proto3生成的代码不同之处都会高亮标出 --- 需要注意的是这些不同之处只是生成的代码中的不同,而不是消息类/接口(同一版本的编译器生成的是一样的)的不同。开始之前,你应该先了解proto2 language guide或proto3 language guide。
编译器调用
使用--cpp_out=命令行参数,Protocol Buffer编译器会生成C++输出。--cpp_out=选项的参数是你要存放C++输出的目录。编译器会为每个.proto文件生成一个头文件和实现文件。输出文件的名称与给定的.proto文件名称有关:
- 后缀(
.proto)被替换成.pb.h(头文件)或pb.cc(实现文件)。 - proto路径(通过
--proto_path或-I指定)被输出路径(通过--cpp_out指定)替换。
例如,调用如下命令:
protoc --proto_path=src --cpp_out=build/gen src/foo.proto src/bar/baz.proto
编译器读取文件src/foo.proto和src/bar/baz.proto并产生4个输出文件:build/gen/foo.pb.h、build/gen/foo.pb.cc、build/gen/bar/baz.pb.h和build/gen/bar/baz.pb.cc。需要的话,编译器会自动生成build/gen/bar目录,但是并不会创建build或build/gen,因此,它们必须已存在。
包
如果.proto文件包含package声明,那么文件中所有的内容都会被放在对应的C++命名空间中。例如,给定package声明:
pakcage foo.bar
文件中的所有声明都会放在foo::bar命名空间中。
消息
如下,是一个简单的消息声明:
message Foo {}
编译器会生成一个名为Foo的类,派生自google::protobuf::Message。这个类是一个具体的类,不存在为实现的纯虚函数。取决与优化模式,Message中的虚函数会/不会被Foo重写。默认情况下,Foo实现所有方法的特定版本以获得最快速度。但是,如果.proto文件中包含:
option optimize_for = CODE_SIZE;
之后Foo只重写功能所需的最小方法集,剩下的靠基本的反射实现。这会显著减小生成代码的大小,但会降低性能。或者,如果.proto文件中包含:
option optimize_for = LITE_RUNTIME;
之后Foo会包含所有方法的快速实现,但实现的是google::protobuf::MessageLite的接口,它只是Message方法的一个子集。需要特意说明的是,它不支持描述符或反射。但是,这种模式下,生成的代码只需链接libprotobuf-lite.so(Windows下libprotobuf-lite.lib)即可,而不是libprotobuf.so(libprotobuf.lib)。“lite”版本的库比完整的库要小的多,特别适合像手机这样的资源有限的系统。
你不应该创建自己的Foo子类。如果你创建了子类且重写了虚函数,重写的函数可能会被忽略,因为许多生成的方法调用被去虚拟胡以提高性能。
Message接口定义了可以让你检查、操作、读写整个消息的方法,包括从二进制字符串中解析和序列化为二进制字符串。
bool ParseFromString(const string& data):从给定的序列化后的二进制字符串(即wire格式)解析消息。bool SerializeToString(string* output):将给定的消息序列化为二进制字符串。string DebugString():返回字符串,文本格式的proto表述(只应在debugging时使用)。
作为上述方法的补充,Foo类定义了下列方法:
Foo():默认构造函数。~Foo():默认析构函数。Foo(const Foo& other):拷贝构造。Foo& operator=(const Foo& other):赋值运算符。void Swap(Foo* other):与另一消息交换信息。const UnknownFieldSet& unknown_fields() const:返回解析消息遇到的未知字段的集合。
Foo类还定义了下面的静态方法:
static const Descriptor* descriptor():返回类型的描述,包含该类型的信息,包括有什么字段以及它们的类型。用于反射时,可以以编程的方式来检查字段。static const Foo& default_instance():返回一个单例模式的Foo实例,它与新构造的Foo实例相同(所以所有的单个字段都是未设置的,所有的重复字段都是空的)。。注意,通过调用New()方法,消息的默认实例可以当作工厂使用。
可以在一个消息中声明另一个消息,就像message Foo { message Bar { } }。
这种情况下,编译器会生成两个类:Foo和Foo_Bar。额外地,编译器会在Foo类中生成如下的typedef:
typedef Foo_Bar Bar;
这意味着你可以像使用内嵌类Foo::Bar那样使用内嵌类型的类。但是,注意C++不允许内嵌类型被前向声明。如果要在另一个文件中使用前向声明Bar并使用该声明,则必须将其标识为Foo_Bar。
字段
补充之前的章节,Protocol Buffer编译器会为.proto文件中定义的每个字段生成一系列的访问方法。
与访问方法一样,编译器为每个包含包含其字段序号的字段生成一个整数常量。常量名是字母k,后跟转换成首字母大写的字段名,之后是FieldNumber。例如,给定字段optional int32 foo_bar = 5;,编译器会生成常量static const int kFooBarFiledNumber = 5;。
对于返回const引用的字段访问器,在调用另一个修改访问器修改消息时,该引用会被调用。这包括调用字段的任意非const访问器,从Message继承的任意非const方法或其它修改修改消息的方法(比如,作为Swap()的参数使用)。相应地,如果在此期间没有对消息进行修改访问,则仅保证在不同的访问方法中返回的引用的地址是相同的。
对于返回指针的字段访问器,在对消息的下一次修改/不修改时,指针可能会失效。这包括调用任何字段的任意访问器、从Message继承的任意方法或通过其它方式访问消息(比如,使用拷贝构造拷贝消息)。相应地,在访问器的两次不同调用之间,返回的指针的值永远不能保证相同。
单个数值字段
对于下面的定义:
int32 foo = 1;
编译器会生成如下方法:
int32 foo() const:返回字段目前的值。如果字段未设置,返回0。void set_foo(int32 value):设置字段的值。调用之后,foo()会返回value。void clear_foo():清空字段的值。调用之后,foo()将返回0。
对于其他数值字段类型(包括bool),根据标量值类型表,int32被相应的c++类型替换。
单个字符串字段
对于任意下面这些定义之一:
string foo = 1;
bytes foo = 1;
编译器会生成如下方法:
const string& foo() const:返回字段当前的值。如果字段未设置,则返回空string/bytes。void set_foo(const string& value):设置字段的值。调用之后,foo()将返回value的拷贝。void set_foo(string&& value)(C++11及之后):设置字段的值,从传入的值中移入。调用之后,foo()将返回value的拷贝。void set_foo(const char* value):使用C格式的空终止字符串设置字段的值。调用之后,foo()将返回value的拷贝。void set_foo(const char* value, int size):如上,但使用的给定的大小而不是寻找空终止符。string* mutable_foo():返回存储字段值的可变string对象的指针。若在字段设置之前调用,则返回空字符串。调用之后,foo()会将写入值返回给给定的字符串。void clear_foo()::清空字段的值。调用之后,foo()将返回空string/bytes。void set_allocated_oo(string* value):设置字段为给定string对象,若已存在,则释放之前的字段值。如果string指针非NULL,消息将获取已分配的string对象的所有权。消息可以在任何时候删除已分配的string对象,因此对该对象的引用可能无效。另外,若value为NULL,该操作与调用clear_foo()效果相同。string* release_foo():释放字段的所有权并返回string对象的指针。调用之后,调用者将获得已分配的string对象的所有权,foo()将返回空string/bytes。
单个枚举字段
给出如下的枚举类型:
enum Bar {
BAR_VALUE = 0;
OTHER_VALUE = 1;
}
对于字段的定义:
Bar foo = 1;
编译器会生成如下方法:
Bar foo() const:返回字段当前的值。如果未设置,则返回默认值(0)。void set_foo(Bar value):设置字段的值。调用之后,foo()将放回value。void clear_foo():清空字段的值。调用之后,foo()返回默认值。
单个内嵌消息字段
给出如下消息类型:
message Bar { }
对于如下定义:
Bar foo = 1;
编译器会生成如下方法:
bool has_foo() const:如果字段已设置,则返回true。const Bar& foo() const:返回字段当前的值。如果字段未设置,则返回一个未设置任何字段的Bar(也许是,Bar::default_instance())。Bar* mutable_foo():返回存储字段值的可变Bar对象的指针。若在字段设置之前调用,则返回一个未设置任何字段的Bar(即,新分配的Bar对象)。调用之后,has_foo()会返回true且foo()返回一个与该实例相同的引用。clear_foo():清空字段的值。调用之后,has_foo()会返回false且foo()返回默认值。void set_allocated_foo(Bar* bar):设置字段为给定bar对象,若已存在,则释放之前的字段值。如果Bar指针非NULL,消息将获取已分配的Bar对象的所有权且has_foo()会返回true。另外,若value为NULL,该操作与调用clear_foo()效果相同。Bar* release_foo():释放字段的所有权并返回Bar对象的指针。调用之后,调用者将获得已分配的Bar对象的所有权且has_foo()会返回false,foo()将返回默认值。
重复的数值字段
对于如下定义:
repeated int32 foo = 1;
编译器会生成如下方法:
int foo_size() const:返回字段中当前元素的数量。int32 foo(int index) const:返回给定的从0开始索引的元素。使用超出[0,foo_size())范围的索引来调用该方法会受到未定义的行为。void set_foo(int index, int32 value): 为给定的从0开始索引的元素赋值。void add_foo(int32 value):将给定的值追加到字段中。void clear_foo():移除字段的所有元素。调用之后,foo_size()将返回0。const RepeatedField<int32>& foo() const:返回存储字段元素的基础RepeatedField。这个容器类提供了类似于STL的迭代器和其他方法。RepeatedField<int32>* mutable_foo():返回存储字段元素的基础RepeatedField的指针。这个容器类提供了类似于STL的迭代器和其他方法。
对于其他数值字段类型(包括bool),根据标量值类型表,int32被相应的c++类型替换。
重复的字符串字段
对于任意下面这些定义之一:
string foo = 1;
bytes foo = 1;
编译器会生成如下方法:
int foo_size() const:返回字段中当前元素的数量。const string& foo(int index) const:返回给定的从0开始索引的元素。使用超出[0,foo_size())范围的索引来调用该方法会受到未定义的行为。void set_foo(int index, const string& value):为给定的从0开始索引的元素赋值。void set_foo(int index, const char* value):使用C风格的空终止符字符串为给定的从0开始索引的元素赋值。void set_foo(int index, const char* value, int size):如上,但使用的给定的大小而不是寻找空终止符。string* mutable_foo(int index):返回给定的从0开始索引的元素所存储的可变string对象的指针。使用超出[0,foo_size())范围的索引来调用该方法会受到未定义的行为。void add_foo(const string& value):使用给定的值为字段追加一个新元素。void add_foo(const char* value):使用给定的C风格的空终止符字符串为字段追加一个新元素。void add_foo(const char* value, int size):如上,但使用的给定的大小而不是寻找空终止符。string* add_foo():新增一个空元素并返回它的指针。void clear_foo():移除字段的所有元素。调用之后,foo_size()将返回0。const RepeatedField<string>& foo() const:返回存储字段元素的基础RepeatedField。这个容器类提供了类似于STL的迭代器和其他方法。RepeatedField<string>* mutable_foo():返回存储字段元素的基础RepeatedField的指针。这个容器类提供了类似于STL的迭代器和其他方法。
重复的枚举字段
给出枚举类型:
enum Bar {
BAR_VALUE = 0;
OTHER_VALUE = 1;
}
定义如下:
repeated Bar foo = 1;
编译器会生成如下方法:
int foo_size() const:返回字段中当前元素的数量。const Bar foo(int index) const:返回给定的从0开始索引的元素。使用超出[0,foo_size())范围的索引来调用该方法会受到未定义的行为。void set_foo(int index, const Bar value):为给定的从0开始索引的元素赋值。void add_foo(const Bar value):使用给定的值为字段追加一个新元素。void clear_foo():移除字段的所有元素。调用之后,foo_size()将返回0。const RepeatedField<int>& foo() const:返回存储字段元素的基础RepeatedField。这个容器类提供了类似于STL的迭代器和其他方法。RepeatedField<int>* mutable_foo():返回存储字段元素的基础RepeatedField的指针。这个容器类提供了类似于STL的迭代器和其他方法。
重复的内嵌消息字段
给出消息定义:
message Bar { }
定义如下:
repeated Bar foo = 1;
编译器会生成如下方法:
int foo_size() const:返回字段中当前元素的数量。const Bar& foo(int index) const:返回给定的从0开始索引的元素。使用超出[0,foo_size())范围的索引来调用该方法会受到未定义的行为。Bar* mutable_foo(int index):返回给定的从0开始索引的元素所存储的可变Bar对象的指针。使用超出[0,foo_size())范围的索引来调用该方法会受到未定义的行为。Bar* add_foo():新增一个空元素并返回它的指针。返回的Bar是可变的,且它的字段全都未设置(即,新分配的Bar对象)。void clear_foo():移除字段的所有元素。调用之后,foo_size()将返回0。const RepeatedField<Bar>& foo() const:返回存储字段元素的基础RepeatedField。这个容器类提供了类似于STL的迭代器和其他方法。RepeatedField<Bar>* mutable_foo():返回存储字段元素的基础RepeatedField的指针。这个容器类提供了类似于STL的迭代器和其他方法。
Oneof数值字段
oneof字段定义如下:
oneof oneof_name {
int32 foo = 1;
}
编译器会生成如下方法:
int32 foo() const:如果oneof case未kFoo,则返回字段当前的值,否则返回默认值。void set_foo(int32 value):- 如果同一oneof字段的其他任一oneof已设置,则调用
clear_oneof_name()。 - 设置字段的值,并设置oneof case为
kFoo。
- 如果同一oneof字段的其他任一oneof已设置,则调用
void clear_foo():- 如果oneof case不为
kFoo,则不做任何操作。 - 如果oneof case为
kFoo,清理字段的值及oneof case。
- 如果oneof case不为
对于其他数值字段类型(包括bool),根据标量值类型表,int32被相应的c++类型替换。
Oneof字符串字段
对于下面任意一个oneof字段定义:
oneof oneof_name {
string foo = 1;
…
}
oneof oneof_name {
bytes foo = 1;
….
}
编译器会生成如下方法:
const string& foo() const:如果oneof case未kFoo,则返回字段当前的值,否则返回默认值。void set_foo(const string& value):- 如果同一oneof字段的其他任一oneof已设置,则调用
clear_oneof_name()。 - 设置字段的值,并设置oneof case为
kFoo。
- 如果同一oneof字段的其他任一oneof已设置,则调用
void set_foo(const char* value):- 如果同一oneof字段的其他任一oneof已设置,则调用
clear_oneof_name()。 - 使用C风格的空终止符字符串来设置字段的值,并设置oneof case为
kFoo。
- 如果同一oneof字段的其他任一oneof已设置,则调用
void set_foo(const char* value, int size):如上,但使用的给定的大小而不是寻找空终止符。string* mutable_foo():- 如果同一oneof字段的其他任一oneof已设置,则调用
clear_oneof_name()。 - 设置oneof case为
kFoo,并返回存储字段值的可变string对象的指针。如果调用之前oneof case没有设置为kFoo,将会返回空字符串(而不是默认值)。
- 如果同一oneof字段的其他任一oneof已设置,则调用
void clear_foo():- 如果oneof case不为
kFoo,则不做任何操作。 - 如果oneof case为
kFoo,清理字段的值及oneof case。
- 如果oneof case不为
void set_allocated_foo(string* value):- 调用
clear_oneof_name()。 - 如果字符串指针非空:将字符串对象设置给字段并设置oneof case为
kFoo。该消息取得已分配字符串对象的所有权。
- 调用
string* release_foo():- 如果oneof case不为
kFoo,则返回NULL。 - 清理oneof case,释放该字段的所有权并返回该字符串对象的指针。调用之后,调用者获得已分配字符串对象的所以权。
- 如果oneof case不为
Oneof枚举字段
给定枚举类型:
enum Bar {
BAR_VALUE = 0;
OTHER_VALUE = 1;
}
oneof字段定义如下:
oneof oneof_name {
Bar foo = 1;
...
}
编译器会生成如下方法:
Bar foo() const:如果oneof case未kFoo,则返回字段当前的值,否则返回默认值。void set_foo(Bar value):- 如果同一oneof字段的其他任一oneof已设置,则调用
clear_oneof_name()。 - 设置字段的值,并设置oneof case为
kFoo。 - 在debug模式下(即NDEBUG未定义),如果
value与Bar中所有的值定义都不匹配,该方法会终端进程。
- 如果同一oneof字段的其他任一oneof已设置,则调用
void clear_foo():- 如果oneof case不为
kFoo,则不做任何操作。 - 如果oneof case为
kFoo,清理字段的值及oneof case。
- 如果oneof case不为
Oneof内嵌消息字段
给定消息类型:
message Bar { }
oneof字段定义如下:
oneof oneof_name {
Bar foo = 1;
...
}
编译器会生成如下方法:
bool has_foo() const:如果oneof case未kFoo,则返回true。const Bar& foo() const:如果oneof case未kFoo,则返回字段当前的值,否则返回Bar::default_instance()。Bar* mutable_foo():- 如果同一oneof字段的其他任一oneof已设置,则调用
clear_oneof_name()。 - 设置oneof case为
kFoo,且返回存储字段值的可变Bar对象的指针。如果调用之前oneof case没有设置为kFoo,则返回所有字段均未设置的Bar(即新分配的Bar)。 - 调用之后,
has_foo()会返回true,foo()会返回一个相同的Bar实例的引用且oneof_name_case()会返回kFoo。
- 如果同一oneof字段的其他任一oneof已设置,则调用
void clear_foo():- 如果oneof case不为
kFoo,则不做任何操作。 - 如果oneof case为
kFoo,清理字段的值及oneof case。
- 如果oneof case不为
void set_allocated_foo(Bar* value):- 调用
clear_oneof_name()。 - 如果
Bar指针非空:将Bar对象设置给字段并设置oneof case为kFoo。该消息取得已分配字符串对象的所有权,has_foo()会返回true,且oneof_name_case()会返回kFoo。 - 如果
Bar指针为空,则has_foo()会返回false,且oneof_name_case()会返回ONEOF_NAME_NOT_SET。(与调用clear_oneof_name()行为类似)
- 调用
Bar* release_foo():- 如果oneof case不为
kFoo,则返回NULL。 - 如果oneof case为
kFoo,清理oneof case,释放该字段的所有权并返回该Bar对象的指针。调用之后,调用者获得已分配Bar对象的所以权。has_foo()会返回false,foo()会返回默认值且oneof_name_case()会返回ONEOF_NAME_NOT_SET。
- 如果oneof case不为
映射字段
映射字段定义如下:
map<int32, int32> weight = 1;
编译器会生成下列访问器方法:
const google::protobuf::Map<int32, int32>& weight();:返回一个不可变的Map。google::protobuf::Map<int32, int32>* weight();:返回一个可变的Map。
在Protocol Buffer中,google::protobuf::Map是用来存储映射字段的特定容器。从下面的接口可以看出,它使用std::map和std::unordered_map的常用方法的子集。
template<typename Key, typename T> {
class Map {
// Member types
typedef Key key_type;
typedef T mapped_type;
typedef MapPair< Key, T > value_type;
// Iterators
iterator begin();
const_iterator begin() const;
const_iterator cbegin() const;
iterator end();
const_iterator end() const;
const_iterator cend() const;
// Capacity
int size() const;
bool empty() const;
// Element access
T& operator[](const Key& key);
const T& at(const Key& key) const;
T& at(const Key& key);
// Lookup
int count(const Key& key) const;
const_iterator find(const Key& key) const;
iterator find(const Key& key);
// Modifiers
pair<iterator, bool> insert(const value_type& value);
template<class InputIt>
void insert(InputIt first, InputIt last);
size_type erase(const Key& Key);
iterator erase(const_iterator pos);
iterator erase(const_iterator first, const_iterator last);
void clear();
// Copy
Map(const Map& other);
Map& operator=(const Map& other);
}
新增数据的最简单的方法就是使用常用的map语法,例如:
std::unique_ptr<ProtoName> my_enclosing_proto(new ProtoName);
(*my_enclosing_proto->mutable_weight())[my_key] = my_value;
pair<iterator, bool> insert(const value_type& value)会隐式调用value_type实例的深拷贝。如下,是向google::protobuf::Map插入新值最高效的方法:
T& operator[](const Key& key): map[new_key] = new_mapped;
在标准map中使用google::protobuf::Map
google::protobuf::Map支持与std::map和std::unordered_map一样的迭代器。如果你不想直接使用google::protobuf::Map,你可以使用如下操作将google::protobuf::Map转化为标准的map:
std::map<int32, int32> standard_map(message.weight().begin(),
message.weight().end());
注意,这将生成为整个映射生成一个深拷贝。
你也可以用下面的方式将标准的map结构化为google::protobuf::Map:
google::protobuf::Map<int32, int32> weight(standard_map.begin(), standard_map.end());
解析未知变量
在网络上,.proto映射相当于每个键值对的映射条目消息,而映射本身是映射条目的重复字段。就像普通的消息类型,解析过的映射条目消息中可能有未知字段:在映射中,int64字段被定义为map<int32, string>。
在网络格式中,如果一个映射条目消息中有未知字段,未知字段将会被丢弃。
如果一个映射条目消息中有一个未知的枚举变量,proto2和proto3有着不同的处理方式。在proto2中,整个映射条目消息将被放入包含消息的未知字段集中。在proto3中,未知的枚举变量会像已知的一样被放入映射字段中。
Any
给出如下的Any定义:
import "google/protobuf/any.proto";
message ErrorStatus {
string message = 1;
google.protobuf.Any details = 2;
}
在生成的代码中,获取字段的detials的getter方法返回一个google::protobuf::Any的实例,它提供如下的用于打包和解包Any变量的特定方法:
class Any {
public:
// Packs the given message into this Any using the default type URL
// prefix “type.googleapis.com”.
void PackFrom(const google::protobuf::Message& message);
// Packs the given message into this Any using the given type URL
// prefix.
void PackFrom(const google::protobuf::Message& message,
const string& type_url_prefix);
// Unpacks this Any to a Message. Returns false if this Any
// represents a different protobuf type or parsing fails.
bool UnpackTo(google::protobuf::Message* message) const;
// Returns true if this Any represents the given protobuf type.
template<typename T> bool Is() const;
}
Oneof
给出如下的oneof定义:
oneof oneof_name {
int32 foo_int = 4;
string foo_string = 9;
...
}
编译器将生成如下的C++枚举类型:
enum OneofNameCase {
kFooInt = 4,
kFooString = 9,
ONEOF_NAME_NOT_SET = 0
}
此外,还会生成这些方法:
OneofNameCase oneof_name_case() const:如果字段被设置了,则返回对于的枚举值;否则,返回ONEOF_NAME_NOT_SET。void clear_oneof_name():如果oneof字段使用指针设置(消息或字符串),则释放该指针,且将oneof case设置为ONEOF_NAME_NOT_SET。
枚举
给出如下的枚举定义:
enum Foo {
VALUE_A = 0;
VALUE_B = 5;
VALUE_C = 1234;
}
编译器会生成名为Foo的C++枚举类型,其值与设置的一样。此外,还会生成下面的函数:
const EnumDescriptor* Foo_descriptor():返回该类型的描述,包括该枚举类型定义的变量的信息。bool Foo_IsValid(int value):如果给定的数字与Foo中定义的值匹配则返回true。const string& Foo_Name(int value):返回给定数字的名称。如果该值不存在则返回空字符串。如果多个值使用这个数字,则返回定义的第一个。在上面的例子中,Foo_Name(5)返回VALUE_B。bool Foo_Parse(const string& name, Foo* value):如果name在该枚举中可用,则将值赋值给value并返回true。在上面的例子中,Foo_Parse("VALUE_C", &some_foo)会返回true,且设置some_foo为1234。const Foo Foo_MIN:该枚举类型中的最小可用值(示例中为VALUE_A)。const Foo Foo_MAX:该枚举类型中的最大可用值(示例中为VALUE_C)。const Foo Foo_ARRAYSIZE:总是被定义为Foo_MAX+1。
你可以在消息类型中定义一个枚举。这种情况下,编译器生成的代码是将它声明为消息类的内嵌枚举类型。Foo_descriptor()和Foo_IsValid()会被声明为静态函数。实际上,枚举类型本身和它的值使用重组后的名称被声明为全局范围可用,使用typedef和一些常量定义的方式导入类的范围。这样做只是为了避免声明排序的问题。假如枚举真的被内嵌到消息类型中,不要依赖重组后的头部名称。
扩展(仅proto2)
给出带有扩展范围的消息类型:
message Foo {
extensions 100 to 199;
}
编译器会为Foo生产一些额外的方法:HasExtension(),ExtensionSize(),ClearExtension(),GetExtension(),SetExtension(),MutableExtension(),AddExtension(),SetAllocatedExtension()和ReleaseExtension()。每个方法的第一个参数是一个扩展标识符(如下所述),它标识一个扩展字段。其余的参数和返回值与对应的访问方法的参数和返回值完全相同,这些访问方法将为与扩展标识符类型相同的普通(非扩展)字段生成。(GetExtension()对应于没有特殊前缀的访问器。)
给出如下的扩展定义:
extend Foo {
optional int32 bar = 123;
repeated int32 repeated_bar = 124;
}
对于单个的扩展字段bar,编译器生成一个名为bar的“扩展标识符”,你可以使用Foo的访问器来访问该扩展,如下:
Foo foo;
assert(!foo.HasExtension(bar));
foo.SetExtension(bar, 1);
assert(foo.HasExtension(bar));
assert(foo.GetExtension(bar) == 1);
foo.ClearExtension(bar);
assert(!foo.HasExtension(bar));
类似地,对于重复字段repeated_bar,编译器生成一个名为repeated_bar的“扩展标识符”,你可以使用Foo的访问器来访问它:
Foo foo;
for (int i = 0; i < kSize; ++i) {
foo.AddExtension(repeated_bar, i)
}
assert(foo.ExtensionSize(repeated_bar) == kSize)
for (int i = 0; i < kSize; ++i) {
assert(foo.GetExtension(repeated_bar, i) == i)
}
(扩展标识符的确切实现是复杂的,涉及到模板的神奇使用——但是,您不需要担心扩展标识符是如何使用它们的。)
扩展可以声明为其它类型的内嵌类型。例如,常见的模式如下:
message Baz {
extend Foo {
optional Baz foo_ext = 124;
}
}
这种情况下,扩展标识符foo_ext被声明为Baz的内嵌类型。使用方法如下:
Foo foo;
Baz* baz = foo.MutableExtension(Baz::foo_ext);
FillInMyBaz(baz);
更多信息,参见这里。
Proto3:C++代码生成指南的更多相关文章
- Proto3:Arena分配指南
Arena分配是仅C++有的功能,在使用Protocol Buffer时,它可以帮助你优化你的内存使用,提高性能.在.proto文件中启用Arena分配会在生成的C++代码中添加处理Arena分配的额 ...
- Protobuf语言指南(转)
Protobuf语言指南 l 定义一个消息(message)类型 l 标量值类型 l Optional 的字段及默认值 l 枚举 l 使用其他消息类型 l 嵌套类型 l 更新一个消息类型 ...
- Protobuf语言指南
Protobuf语言指南 l 定义一个消息(message)类型 l 标量值类型 l Optional 的字段及默认值 l 枚举 l 使用其他消息类型 l 嵌套类型 l 更新一个消息类型 ...
- Protobuf 语法指南
英文: Proto Buffers Language Guide 本指南描述了怎样使用protocol buffer 语法来构造你的protocol buffer数据,包括.proto文件语法以及怎样 ...
- protobuf 更新消息和扩展,包
一.更新一个消息类型 如果一个已有的消息格式已无法满足新的需求--如,要在消息中添加一个额外的字段--但是同时旧版本写的代码仍然可用.不用担心!更新消息而不破坏已有代码是非常简单的.在更新时只要记住以 ...
- Protobuf 语言指南(proto3)
Protobuf 语言指南(proto3) Protocol Buffer是Google的语言中立的,平台中立的,可扩展机制的,用于序列化结构化数据 - 对比XML,但更小,更快,更简单.您可以定义数 ...
- Proto3使用指南
这篇指南讲述如何使用Protocol Buffers来结构化你的Protocol Buffer数据,包括.proto文件语法以及如何从.proto文件生成你的访问类型.本文主要涵盖了proto3的语法 ...
- 开发指南专题六:JEECG微云高速开发平台代码生成
开发指南专题六:JEECG微云高速开发平台代码生 1.1. 代码生成扫描路径配置 用代码生成器生成代码后.须要进行相关配置配置,扫描注入control.service.entity等; 具体操作过程例 ...
- NewLife.XCode 上手指南2018版(一)代码生成
目录 NewLife.XCode 上手指南2018版(一)代码生成 NewLife.XCode 上手指南2018版(二)增 NewLife.XCode 上手指南2018版(三)查 NewLife.XC ...
随机推荐
- 时间API
1. 时间API 我们的时间在java里是long类型的整数,这个整数称之为时间戳(也叫格林威治时间),即从1970-01-01到现在为止所经过的毫秒数,单有这个时间戳是不能准确表达世界各地的时间,还 ...
- IT人员职业发展规划
- Codeforces1303F Number of Components
Description link 题意:给一个全\(0\)矩阵,每次支持一个修改,修改不还原(这要是还原了不就成\(A\)题了) 然后询问每一次修改完了当前矩阵的连通块个数 每一个修改的值单调不降 修 ...
- BZOJ2733 [HNOI2012]永无乡(并查集+线段树合并)
题目大意: 在$n$个带权点上维护两个操作: 1)在点$u,v$间连一条边: 2)询问点$u$所在联通块中权值第$k$小的点的编号,若该联通块中的点的数目小于$k$,则输出$-1$: 传送门 上周的模 ...
- php面向对象理解(一)
常用的继承过程,以及对public.private.protected修饰符的理解: /*****************************父类************************* ...
- Nginx_安全2
Nginx与安全有关的配置 隐藏版本号 http { server_tokens off;} 经常会有针对某个版本的nginx安全漏洞出现,隐藏nginx版本号就成了主要的安全优化手段之一,当然 ...
- Python - 使用 PostgreSQL 数据库
基本用法 # -*- coding: utf-8 -*- # !/usr/bin/python # 需要安装下面的驱动包 import psycopg2 # 连接到一个现有的数据库,如果数据库不存在, ...
- poj 1463树形dp 树的最小覆盖
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #i ...
- springboot系列教程导学篇
spring boot2.0系列教程学习之导学篇 springboot 2.0深度学习系列教程. Spring Boot 虽然凯哥从2015年年初开始就接触了spring boot.但是在之后的公司中 ...
- ABC:Meaningful Mean
题目描述 You are given an integer sequence of length N, a= {a1,a2,…,aN}, and an integer K. a has N(N+1)⁄ ...