本章节实际上是介绍Protocol Buffer编译器从给定的protocol定义中生成的C++代码。所有proto2和proto3生成的代码不同之处都会高亮标出 --- 需要注意的是这些不同之处只是生成的代码中的不同,而不是消息类/接口(同一版本的编译器生成的是一样的)的不同。开始之前,你应该先了解proto2 language guideproto3 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.protosrc/bar/baz.proto并产生4个输出文件:build/gen/foo.pb.hbuild/gen/foo.pb.ccbuild/gen/bar/baz.pb.hbuild/gen/bar/baz.pb.cc。需要的话,编译器会自动生成build/gen/bar目录,但是并不会创建buildbuild/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.solibprotobuf.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 { } }

这种情况下,编译器会生成两个类:FooFoo_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对象,因此对该对象的引用可能无效。另外,若valueNULL,该操作与调用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()会返回truefoo()返回一个与该实例相同的引用。
  • clear_foo():清空字段的值。调用之后,has_foo()会返回falsefoo()返回默认值。
  • void set_allocated_foo(Bar* bar):设置字段为给定bar对象,若已存在,则释放之前的字段值。如果Bar指针非NULL,消息将获取已分配的Bar对象的所有权且has_foo()会返回true。另外,若valueNULL,该操作与调用clear_foo()效果相同。
  • Bar* release_foo():释放字段的所有权并返回Bar对象的指针。调用之后,调用者将获得已分配的Bar对象的所有权且has_foo()会返回falsefoo()将返回默认值。

重复的数值字段

对于如下定义:

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
  • void clear_foo()
    • 如果oneof case不为kFoo,则不做任何操作。
    • 如果oneof case为kFoo,清理字段的值及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
  • void set_foo(const char* value)
    • 如果同一oneof字段的其他任一oneof已设置,则调用clear_oneof_name()
    • 使用C风格的空终止符字符串来设置字段的值,并设置oneof case为kFoo
  • void set_foo(const char* value, int size):如上,但使用的给定的大小而不是寻找空终止符。
  • string* mutable_foo()
    • 如果同一oneof字段的其他任一oneof已设置,则调用clear_oneof_name()
    • 设置oneof case为kFoo,并返回存储字段值的可变string对象的指针。如果调用之前oneof case没有设置为kFoo,将会返回空字符串(而不是默认值)。
  • void clear_foo()
    • 如果oneof case不为kFoo,则不做任何操作。
    • 如果oneof case为kFoo,清理字段的值及oneof case。
  • void set_allocated_foo(string* value)
    • 调用clear_oneof_name()
    • 如果字符串指针非空:将字符串对象设置给字段并设置oneof case为kFoo。该消息取得已分配字符串对象的所有权。
  • string* release_foo()
    • 如果oneof case不为kFoo,则返回NULL
    • 清理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未定义),如果valueBar中所有的值定义都不匹配,该方法会终端进程。
  • void clear_foo()
    • 如果oneof case不为kFoo,则不做任何操作。
    • 如果oneof case为kFoo,清理字段的值及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()会返回truefoo()会返回一个相同的Bar实例的引用且oneof_name_case()会返回kFoo
  • void clear_foo()
    • 如果oneof case不为kFoo,则不做任何操作。
    • 如果oneof case为kFoo,清理字段的值及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()会返回falsefoo()会返回默认值且oneof_name_case()会返回ONEOF_NAME_NOT_SET

映射字段

映射字段定义如下:

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::mapstd::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::mapstd::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++代码生成指南的更多相关文章

  1. Proto3:Arena分配指南

    Arena分配是仅C++有的功能,在使用Protocol Buffer时,它可以帮助你优化你的内存使用,提高性能.在.proto文件中启用Arena分配会在生成的C++代码中添加处理Arena分配的额 ...

  2. Protobuf语言指南(转)

    Protobuf语言指南 l  定义一个消息(message)类型 l  标量值类型 l  Optional 的字段及默认值 l  枚举 l  使用其他消息类型 l  嵌套类型 l  更新一个消息类型 ...

  3. Protobuf语言指南

    Protobuf语言指南 l  定义一个消息(message)类型 l  标量值类型 l  Optional 的字段及默认值 l  枚举 l  使用其他消息类型 l  嵌套类型 l  更新一个消息类型 ...

  4. Protobuf 语法指南

    英文: Proto Buffers Language Guide 本指南描述了怎样使用protocol buffer 语法来构造你的protocol buffer数据,包括.proto文件语法以及怎样 ...

  5. protobuf 更新消息和扩展,包

    一.更新一个消息类型 如果一个已有的消息格式已无法满足新的需求--如,要在消息中添加一个额外的字段--但是同时旧版本写的代码仍然可用.不用担心!更新消息而不破坏已有代码是非常简单的.在更新时只要记住以 ...

  6. Protobuf 语言指南(proto3)

    Protobuf 语言指南(proto3) Protocol Buffer是Google的语言中立的,平台中立的,可扩展机制的,用于序列化结构化数据 - 对比XML,但更小,更快,更简单.您可以定义数 ...

  7. Proto3使用指南

    这篇指南讲述如何使用Protocol Buffers来结构化你的Protocol Buffer数据,包括.proto文件语法以及如何从.proto文件生成你的访问类型.本文主要涵盖了proto3的语法 ...

  8. 开发指南专题六:JEECG微云高速开发平台代码生成

    开发指南专题六:JEECG微云高速开发平台代码生 1.1. 代码生成扫描路径配置 用代码生成器生成代码后.须要进行相关配置配置,扫描注入control.service.entity等; 具体操作过程例 ...

  9. NewLife.XCode 上手指南2018版(一)代码生成

    目录 NewLife.XCode 上手指南2018版(一)代码生成 NewLife.XCode 上手指南2018版(二)增 NewLife.XCode 上手指南2018版(三)查 NewLife.XC ...

随机推荐

  1. BBS登录功能

    BBS登录功能 一.后端实现 1.实现验证码 from PIL import Image, ImageDraw, ImageFont import random from io import Byte ...

  2. 安装R的h5包

    系统 redhat7 安装H5的包, 依赖系统hdf5包,如下安装完, 发现版本不一致, sudo yum install hdf5-devel 解决办法: 移花接木 sudo ln -s /opt/ ...

  3. 01 - CentOS 中安装Python 2.7.16

    准备 下载链接:https://www.python.org/ftp/python/ 下载源码:wget https://www.python.org/ftp/python/2.7.16/Python ...

  4. Vue项目中跨域问题解决

    后台更改header 使用http-proxy-middleware 代理解决(项目使用vue-cli脚手架搭建) Jquery jsonp 一.后台更改header header('Access-C ...

  5. Linux基础篇三:文件系统

    /bin      实际上是  /usr/bin /sbin    实际上是  /usr/sbin /usr/bin 里面的命令其实是依赖  /lib64  或者    /lib32 ldd  /us ...

  6. EF 执行存储过程

  7. sql 新增随机数

    update RemoteDetection set humidity=round((rand()*3+29),0),TEMPERATURE=round((rand()*3+16),0),atmosp ...

  8. z-scores|zα

    6.2 Areas Under the Standard Normal Curve Property 4: Almost all the area under the standard normal ...

  9. Java面试题2-附答案

    JVM的内存结构 根据 JVM 规范,JVM 内存共分为虚拟机栈.堆.方法区.程序计数器.本地方法栈五个部分. 1.Java虚拟机栈: 线程私有:每个方法在执行的时候会创建一个栈帧,存储了局部变量表, ...

  10. <USACO09DEC>视频游戏的麻烦Video Game Troublesの思路

    emm今天模拟赛的题.神奇地A了 #include<cstdio> #include<cstring> #include<iostream> #include< ...