参考网页
Protobuf-Generated Code Guide
【方便自用的翻译笔记,不保准确,欢迎交流】
Protobuf生成的C++代码说明
详细描述protocol buffer编译器生成的C++代码中所有的protocol定义。
proto2与proto3生成的代码的区别都进行了标注,这些区别存在于本文档所描述的生成的代码之中,并不存在于基本的消息类或消息接口,因为这部分两者是相同的。
调用编译器
编译时命令行加入--cpp_out=
可使编译器生成C++代码。--cpp_out=
所需的参数是期望保存生成的代码的地址。编译器会给每一个输入的.proto
文件生成一个头文件和实现文件。生成的文件的名字来自于.proto
文件,并进行两项改动:
- 文件原扩展名
.proto
会根据生成的头文件或实现文件属性被替换为.ph.h
或.pb.cc
。 - 根据标志位
--proto_path=
或-I
指定的proto路径会被替换为由标志位--cpp_out=
指定的路径。
例如以下调用编译器的命令:
proto --proto_path=src --cpp_out=build/gen src/foo.proto src/bar/baz.proto
编译器读取文件src/foo.proto
和src/bar/baz.proto
并生成四个文件: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
不会被自动创建,必须提前创建好。
Packages
若.proto
文件中声明了一个package
,则文件内所有条目都将被放置于相关的C++命名空间之中。例如定义这样一个package
:
package foo.bar
则文件内其他所有的声明都将存在于命名空间foo::bar
。
Messages
简单声明一个消息:
message Foo {}
protocol buffer将生成一个名为Foo
的类,该类以public形式派生自google::protobuf::Message
。该类是实例化的类,其中的纯虚函数全都有实现。根据不同的优化模式(optimization mode),在Message
中非纯虚函数的虚函数可能还没有被Foo
重写(override)。默认情况下,Foo
会用专用的版本实现所有方法,以达到最快的速度。但如果.proto
文件含有以下语句:
option optimize_for = CODE_SIZE;
则Foo
只会重写函数必需的最少数目的方法,其余方法则依靠基于反射(reflection-based)的实现。此外,如果.proto
文件包含以下语句:
option optimize_for = LITE_RUNTIME;
则Foo
将包含所有方法的快速实现,但会实现google::protobuf::MessageLite
接口,这个接口仅有Message
方法的一个子集。特别地,此类不支持描述符(descriptors)和反射机制(reflection)。然而,在此模式下,所生成的代码只需要链接libprotobuf-lite.so
(Windows系统链接libprotobuf-lite.lib
)而非libprotobuf.so
(libprotobuf.lib
)。名为“lite”的库远小于完整的库,也更适合像手机这样资源受限的系统。
不能创建自定义的Foo
子类,如果基于此类创建了子类并重写了虚函数的实现,则这个实现有可能会被忽略,因为为了提高性能,很多对生成方法的调用都是非虚的。
Message
接口定义了很多方法,可以实现对整个消息的检查、转移、读取和写入,也包含从二进制字符串解析出消息或将消息序列化成二进制字符串。
下面的函数将输入的二进制字符串(或称为有线格式wire format)解析出消息内容:
bool ParseFromString(const string& data)
下面的函数将消息序列化成二进制字符串:
bool SerializeToString(string* output) const
下面的函数以text_format
形式返回proto的表现信息(仅用于debug)
string DebugString()
此外,Foo
类中还定义了以下函数:
Foo()
:默认构造函数
~Foo()
:默认析构函数
Foo(const Foo& other)
:复制构造函数
Foo(Foo&& other)
:转移构造函数
Foo& operator=(const Foo& other)
:赋值运算符
Foo& operator=(Foo&& other)
:转移赋值运算符
void Swap(Foo* other)
:与另一个消息交换内容
const UnknownFieldSet& unknown_fields() const
:返回在解析消息的过程中遇到的未知字段的集合。如果在.proto
文件中指定了option optimize_for = LITE_RUNTIME
则将返回值类型改为std::string&
。
UnknownFieldSet* mutable_unknown_fields()
:返回一个指针,其指向消息解析过程中遇到的未知字段的可变集合。如果在.proto
文件中指定了option optimize_for = LITE_RUNTIME
则将返回值类型改为std::string*
。
该类中也定义了如下静态函数:
static const Descriptor* descriptor()
:返回一个类型(type)的描述符。该描述符说明了这个类型里有哪些字段以及这些字段分别是什么类型。此方法可与反射机制一起使用,通过编程方式检查字段。
static const Foo& default_instance()
:返回一个Foo
的常数单例实例,这个实例与新构造的Foo
实例完全一样(由此可使所有单一字段变为未设置的状态,而且所有的重复字段为空)。注意,消息的默认实例可以通过调用其New()
函数将其作为工厂(factory)使用。
生成的文件名(Generated Filenames)
如果使用了系统保留关键字作为某字段名称,则在生成代码时会在其名称后添加下划线。例如,以proto3语法书写的下列定义:
message MyMessage {
string false = 1;
string myFalse = 2;
}
生成的代码中对应的内容则是:
void clear_false_() ;
const std::string& false_() const;
void set_false_(Arg_&& arg, Args_... args);
std::string* mutable_false_();
PROTOBUF_NODISCARD std::string* release_false_();
void set_allocated_false_(std::string* ptr);
void clear_myfalse() ;
const std::string& myfalse() const;
void set_myfalse(Arg_&& arg, Args_... args);
std::string* mutable_myfalse();
PROTOBUF_NODISCARD std::string* release_myfalse();
void set_allocated_myfalse(std::string* ptr);
内嵌类型(Nested Types)
可以在消息内部声明另一个消息,例如:
message Foo {
message Bar {}
}
如果使用这种写法,那么编译器会生成两个类:Foo
和Foo_Bar
。此外,编译器会在Foo
内生成一个typedef:
typedef Foo_Bar Bar;
这就意味着可以将这个内嵌类型的类Bar
当作是嵌套类Foo::Bar
一样使用。但要注意的是,C++不允许对内嵌类型进行前向声明(forward-declared),如果想要在其他文件中对类Bar
进行前向声明并使用这个声明,则应将其标识为Foo_Bar
。
字段(Fields)
除了前述的函数外,protocol buffer编译器还为.proto
文件里的消息中定义的每个字段都生成了访问函数集。这些函数均为小写/蛇形命名,例如has_foo()
和clear_foo()
。
和访问函数一样,编译器会为每个含有号码的字段生成一个整数常量,这个常量名带有字母“k”,字母后是根据驼峰命名法转换的字段名,最后是字段号码FieldNumber
。例如给出定义optional int32 foo_bar = 5;
,则编译器将生成常量static const int kFooBarFieldNumber = 5;
。
对于会返回一个const
引用的访问函数,该引用可能会在下一次对消息的访问进行修改时失效。相关操作包括调用任意字段的任意非常量的访问函数、调用继承自消息的任意非常量函数或者用其他方式对消息进行修改(例如将消息作为Swap()
的参数)。相应地,当没有对消息的访问进行修改时,才能确保访问函数的不同调用下,返回的引用的地址保持一致。
对于返回指针的访问函数,该指针可能会在下一次对消息进行修改的或者非修改的访问时失效。相关操作包括在无视常量的情况下,调用任何字段的任何访问函数、调用任何继承自消息的函数或者其他访问消息的方式(例如使用复制构造函数复制消息)。相应地,在访问函数的不同调用下,返回的指针的值无法保证一致。
可选数值型字段(Optional Numeric Fields, proto2 & 3)
对于下方任意字段的定义:
optional int32 foo = 1;
required int32 foo = 1;
编译器将生成以下访问函数:
bool has_foo() const
:当字段已被设置则返回true。
int32 foo() const
: 返回当前字段的值。如果字段未被赋值,则返回默认值。
void set_foo(int32 value)
:设置字段的值。调用此函数后,has_foo()
将返回true,foo()
将返回所设置的值。
void clear_foo()
:清空字段的值。调用此函数后,has_foo()
将返回false,foo()
将返回默认值。
对于其他数值型字段类型(包括bool),int32可被替换为与标量值类型表中与C++相对应的其他类型,参考类型表:proto3标量类型表,proto2标量类型表。
隐式存在的数值型字段(Implicit Presence Numeric Fields,proto3)
对于以下字段的定义:
optional int32 foo = 1;
int32 foo = 1; // 未指定字段标识符,默认为隐式存在
编译器将生成如下访问函数:
int32 foo() const
: 返回当前字段的值。如果字段未被赋值,则返回默认值。
void set_foo(int32 value)
:设置字段的值。调用此函数后,foo()
将返回所设置的值。
void clear_foo()
:清空字段的值。调用此函数后,foo()
将返回默认值。
可选字符串/字节型字段(Optional String/Bytes Fields, proto2 & 3)
对以下任一字段定义:
optional string foo = 1;
required string foo = 1;
optional bytes foo = 1;
required bytes foo = 1;
编译器将生成如下访问函数:
bool has_foo() const
:当字段已被设置则返回true。
const string& foo() const
:返回当前字段的值。如果字段未被赋值,则返回默认值。
void set_foo(const string& value)
:设置字段的值。调用此函数后,has_foo()
将返回true,foo()
将返回所设置的值的副本。
void set_foo(string&& value)
(C++11及更高版本):从传递的字符串开始设置字段的值。调用此函数后,has_foo()
将返回true,foo()
将返回所设置的值的副本。
void set_foo(const char* value)
:使用C语言类型的以null结尾的字符串设置字段的值。调用此函数后,has_foo()
将返回true,foo()
将返回所设置的值的副本。
void set_foo(const char* value, int size)
:与上一条一样,但字符串大小已显式给出,并非通过查找null终止符字节来确定。
string* mutable_foo()
:返回一个指针,该指针指向存储着字段的值的可变字符串对象。如果在该函数调用前,字段未进行设置,则会返回空的字符串而非默认值。调用此函数后,has_foo()
将返回true,foo()
将返回写入了该字符串字段的任何内容。
void clear_foo()
:清空字段的值。调用此函数后,has_foo()
将返回false,foo()
将返回默认值。
void set_allocated_foo(string* value)
:将字段的值设置为输入的字符串对象,如果字段原本有值,则将原值释放。如果字符串指针非空,则message将获得已分配的字符串对象的所有权,并且has_foo()
将返回true。message在任何时间都能自由地删除已分配的字符串对象,所以对此对象的引用可能会失效。另外,如果输入的值为空,则该函数执行结果与调用clear_foo()
是相同的。
string* release_foo()
:释放字段的所有权并返回字符串对象的指针。调用此函数后,调用者将获得已分配字符串对象的所有权,has_foo()
将返回false,foo()
将返回默认值。
Implicit Presence String/Bytes Fields(proto3)
Singular Bytes Fields with Cord Support
可选枚举字段(Optional Enum Fields,proto2 & 3)
给出如下枚举类型:
enum Bar {
BAR_UNSPECIFIED = 0;
BAR_VALUE = 1;
BAR_OTHER_VALUE = 2;
}
对于以下字段定义:
optional Bar foo = 1;
required Bar foo = 1;
编译器将生成如下访问函数:
bool has_foo() const
:当字段已被设置则返回true。
Bar foo() const
:返回当前字段的值。如果字段未被赋值,则返回默认值。
void set_foo(Bar value)
:设置字段的值。调用此函数后,has_foo()
将返回true,foo()
将返回所设置的值。在debug模式下,若输入的值不符合Bar定义中的任何一个枚举量,则函数将中断程序。
void clear_foo()
:清空字段的值。调用此函数后,has_foo()
将返回false,foo()
将返回默认值。
Implicit Presence Enum Fields(proto3)
可选内嵌式消息型字段(Optional Embedded Message Fields,proto2 & 3)
给出消息类型:
message Bar {}
对于以下字段定义:
// proto2
optional Bar foo = 1;
required Bar foo = 1;
//proto3
Bar foo = 1;
编译器生成如下访问函数:
bool has_foo() const
const Bar& foo() const
:返回当前字段的值。如果字段未被赋值,则返回未被设置的Bar类型变量(可能是Bar::default_instance
)。
Bar* mutable_foo()
:返回一个指针,指向存储了字段数值的可变Bar
对象。如果该字段被调用前未被设置,则返回的Bar
对象不会含有任何字段设置(也就是说会和新分配的Bar
一样)。
void clear_foo()
void set_allocated_foo(Bar* bar)
:将字段原有的值清空,并根据输入的Bar
对象设置字段。如果Bar
指针非空,则消息会获取已分配的Bar
对象的所有权,has_foo()
将返回true。如果Bar
指针为空,则调用本函数和调用clear_foo()
的效果是一样的。
Bar* release_foo()
重复数值型字段
对以下字段定义:
repeated int32 foo = 1;
编译器生成如下访问函数:
int foo_size() const
:返回当前字段内元素的数量。如果要检查字段是否为空,应使用RepeatedField
下属的empty()
函数。
int32 foo(int index) const
:根据输入的下标返回元素值,下标从零开始计数。如果输入的下标不在0~foo_size()范围内,则会引发未定义行为。
void set_foo(int index, int32 value)
:将所给的下标对应的元素值修改为输入的值。
void add_foo(int32 value)
:将输入的值作为新的元素添加到字段末尾。
void clear_foo()
:清除字段内所有元素,调用此函数后,foo_size()
将返回0;
const RepeatedField<int32>& foo() const
:返回存储了字段所有元素的基础RepeatedField
,该容器类提供了类似STL的迭代器和其他函数。
RepeatedField<int32>* mutale_foo()
:返回一个指针,指向存储了字段所有元素的基础可变RepeatedField
,该容器类提供了类似STL的迭代器和其他函数。
重复字符串型字段
对以下字段定义:
repeated string foo = 1;
repeated bytes foo 1;
编译器生成如下访问函数:
int foo_size() const
const string& foo(int index) const
:根据输入的下标返回元素值,下标从零开始计数。如果输入的下标不在0~foo_size()-1范围内,则会引发未定义行为。
void set_foo(int index, const string& value)
void set_foo(int index, const char* value)
:将所给的下标对应的元素值修改为输入的值,输入值是采用C风格的以null结束的字符串。
void set_foo(int index, const char* value, int size)
:与上一个函数作用相同,但字符串大小已经明确给出,无需依靠查找null结尾的字节来确定。
string* mutable_foo(int index)
:返回一个指针,指向存储了下标对应元素值的可变字符串对象。如果输入的下标不在0~foo_size()范围内,则会引发未定义行为。
void add_foo(const string& value)
void add_foo(const char* value)
:将输入的值作为新的元素添加到字段末尾,输入值是采用C风格的以null结束的字符串。
void add_foo(const char* value, int size)
:与上一个函数作用相同,但字符串大小已经明确给出,无需依靠查找null结尾的字节来确定。
string* add_foo()
:在字段末尾添加一个新的空字符串元素,并返回指向此元素的指针。
void clear_foo()
const RepeatedPtrField<string>& foo() const
RepeatedPtrField<string>* mutable_foo()
重复枚举型字段
给出枚举类型:
enum Bar {
BAR_UNSPECIFIED = 0;
BAR_VALUE = 1;
BAR_OTHER_VALUE = 2;
}
对以下字段定义:
repeated Bar foo = 1;
编译器生成如下访问函数:
int foo_size() const
Bar foo(int index) const
void set_foo(int index, Bar value)
void add_foo(Bar value)
:将输入的值作为新的元素添加到字段末尾。在debug模式(NDEBUG未定义),如果输入的值与Bar中的任何值都无法匹配,则该函数会中止程序。
void clear_foo()
const RepeatedField<int>& foo() const
RepeatedField<int>* mutable_foo()
重复内嵌式消息型字段
给出消息类型:
message Bar {}
对以下字段定义:
repeated Bar foo = 1;
编译器生成如下访问函数:
int foo_size() const
const Bar& foo(int index) const
Bar* mutable_foo(int index)
Bar* add_foo()
void clear_foo()
const RepeatedPtrField<Bar>& foo() const
RepeatedPtrField<Bar>* mutable_foo()
oneof数值型字段
对以下定义:
oneof example_name {
int32 foo = 1;
...
}
编译器生成如下访问函数:
bool has_foo() const
(仅proto2生成)
int32 foo() const
void set_foo(int32 value)
void clear_foo()
Oneof String Fields
oneof example_name {
string foo = 1;
...
}
oneof example_name {
bytes foo = 1;
...
}
bool has_foo() const
const string& foo() const
void set_foo(const string& value)
void set_foo(const char* value)
void set_foo(const char* value, int size)
string* mutable_foo()
void clear_foo()
void set_allocated_foo(string* value)
string* release_foo()
Oneof Enum Fields
/
Oneof Embedded Message Fields
/
Map Fields
/
Any
/
Oneof
给出如下oneof定义:
oneof example_name {
int32 foo_int = 4;
string foo_string = 9;
...
}
编译器生成如下C++枚举类型:
enum ExampleNameCase {
kFooInt = 4,
kFooString = 9,
EXAMPLE_NAME_NOT_SET = 0
}
此外,还会生成如下函数:
ExampleNameCase example_name_case() const
:返回枚举类型以表明哪个字段已被设置。如果所有字段均未被设置,则返回EXAMPLE_NAME_NOT_SET
。
void clear_example_name()
:释放使用指针(消息或字符串指针)进行设置的 oneof 字段对象,并将该 oneof 设置为EXAMPLE_NAME_NOT_SET
。
Arena Allocation
Arena Allocation 是一个仅限C++才能使用的功能,在使用 protocol buffer 时可以优化内存使用并提高性能。在 .proto 中启用 arena allocation 后,在C++生成的代码会添加用于处理 arena 的额外代码。参考在《Arena Allocation Guide》了解更多关于arena allocation API 的信息。