本章節實際上是介紹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);
更多信息,參見這里。
