Protobuf: C++ 產生的代碼簡析(Proto3)
前言
參考官方C++ Generated Code文檔,主要是參考了官方文檔。
主要描述protocol buffer編譯器爲Proto3協議定義生成的C++代碼。
編譯器調用
之前的文檔已經描述過,不在關注,如有需要請查看之前的文檔。
Messages
一個簡單的Message:
message Foo {}
protocol buffer 編譯器生成一個名爲foo的類,該類公開繼承自google::protobuf::Message
。該類是一個具體的類;沒有任何純虛擬方法是未實現的。Message
中是虛擬的但不是純虛擬的方法可能會被Foo
覆蓋,這取決於優化模式。默認情況下,Foo
實現所是以最大速度。但是,如果.proto
文件包含行:
option optimize_for = CODE_SIZE;
然後,Foo
將覆蓋運行所需的最小方法集,並依賴於其餘的基於反射的實現。減小了生成的代碼的大小,但也降低了性能。或者,如果.proto
文件包含:
option optimize_for = LITE_RUNTIME;
然後Foo
包括所有方法的快速實現,但將繼續實現google::protobuf::MessageLite
接口,該接口只包含消息方法的一個子集。特別是,它不支持描述符或反射。但是,在這種模式下,生成的代碼只需要與libprotobuf-lite.so
(Windows上的libprotobuf-lite.lib
)鏈接,而不需要與libprotobuf.so
(libprotobuf.lib
)鏈接。“Lite”庫比完整庫小得多,更適合於資源受限的系統,如移動電話。
不應該創建自己的Foo
子類。如果子類化該類並重寫虛擬方法,則override可能被忽略,因爲許多生成的方法調用都是de-virtualized的,以提高性能。
Message
接口定義了允許檢查、操作、讀取或寫入整個消息的方法,包括從解析和序列化爲二進制字符串。
-
bool ParseFromString(const string& data)
:解析給定序列化二進制字符串(也稱爲wire format)中的消息。 -
bool SerializeToString(string* output) const
:將給定消息序列化爲二進制字符串。 -
string DebugString()
:返回一個字符串,該字符串給出proto的“文本格式”表示形式(僅用於調試)。
除了這些方法之外,foo類還定義了以下方法:
-
Foo()
:默認構造函數。 -
~Foo()
:默認析構函數。 -
Foo(const Foo& other)
:複製構造函數。 -
Foo& operator=(const Foo& other)
:賦值運算符。 -
void Swap(Foo* other)
:用另一條消息交換內容。 -
const UnknownFieldSet& unknown_fields() const
:返回分析此消息時遇到的一組未知字段。 -
UnknownFieldSet* mutable_unknown_fields()
:返回一個指針,指向解析此消息時遇到的一組可變未知字段。
類還定義了以下靜態方法:
-
static const Descriptor* descriptor()
:返回類型的描述符。這包含關於類型的信息,包括它有哪些字段以及它們的類型。這可以與反射一起使用,以編程方式檢查字段。 -
static const foo&default_instance()
:返回一個與新構造的foo實例相同的foo的const singleton實例(所有單字段均未設置,所有重複字段均爲空)。請注意,消息的默認實例可以通過調用其New()
方法用作工廠。
message 可以在另一個message 中聲明。例如:message Foo { message Bar { } }
在這種情況下,編譯器生成兩個類:Foo
和Foo_Bar
。此外,編譯器在Foo
內生成typedef
,如下所示:
typedef Foo_Bar Bar;
可以像Foo::Bar
一樣使用嵌套類型的類。但是,請注意C++不允許聲明嵌套類型。如果要在另一個文件中的聲明Bar
並使用該聲明,則必須將其標識爲Foo_Bar
。
字段
除了上述描述的方法外,protocol buffer編譯器還爲.proto
文件中message定義的每個字段生成一組訪問器方法。
除了訪問器方法之外,編譯器還爲每個包含字段號的字段生成一個整型常量。常量名是字母k
,後跟轉換爲駝峯命名法的字段名,後跟FieldNumber
。例如,給定字段optional int32 foo_bar = 5;
,編譯器將生成常量static const int kFooBarFieldNumber = 5;
。
對於返回const
引用的字段訪問器,當對消息進行下一次修改訪問時,該引用可能會失效。這包括調用任何字段的任何非常量訪問器、調用從消息繼承的任何非常量方法或通過其他方式修改消息(例如,使用message
作爲Swap()
的參數)。相應地,如果沒有同時對message進行修改訪問,則返回引用的地址在訪問器的不同調用之間是相同的。
對於返回指針的字段訪問器,當對消息進行下一次修改或非修改訪問時,該指針可能無效。這包括,常量、調用任何字段的任何訪問器、調用從消息繼承的任何方法或通過其他方式訪問消息(例如,使用複製構造函數複製消息)。相應地,返回指針的值在訪問器的兩個不同調用中永遠不會保證是相同的。
單個數字字段(proto3)
對於此字段定義:
Int32 foo=1;
編譯器將生成以下訪問器方法:
-
int32 foo() const:
:返回字段的當前值。如果未設置字段,則返回0。 -
void set_foo(int32 value)
:設置字段的值。調用此函數後,foo()
將返回值。 -
void clear_foo()
:清除字段值。調用此函數後,foo()
將返回0。
單個字符串字段(proto3)
對於這些字段定義中的任何一個:
string foo = 1;
bytes foo = 1;
編譯器將生成以下訪問器方法:
-
const string& foo() const
:返回字段的當前值。如果未設置字段,則返回空字符串/空字節。 -
void set_foo(const string& value)
:設置字段的值。調用此函數後,foo()
將返回值的副本。 -
void set_foo(string&& value)
(C++ 11及以上):設置字段的值,從傳遞的字符串中賦值。調用此函數後,foo()
將返回值的副本。 -
void set_foo(const char* value)
:使用C樣式的以空結尾的字符串設置字段的值。調用此函數後,foo()
將返回值的副本。 -
void set_foo(const char* value, int size)
:如上所述,但字符串大小是顯式給定的,而不是通過查找空終止符字節來確定的。 -
string* mutable_foo()
:返回存儲字段值的可變字符串對象的指針。如果在調用之前未設置字段,則返回的字符串將爲空。調用此函數後,foo()將返回寫入給定字符串的任何值。 -
void clear_foo()
:清除字段值。調用此函數後,foo()
將返回空字符串/空字節。 -
void set_allocated_foo(string* value)
:將string對象設置爲字段,並釋放前一個字段值(如果存在)。如果字符串指針不爲空,則消息將取得已分配字符串對象的所有權。消息可以隨時刪除已分配的字符串對象,因此對該對象的引用可能會無效。否則,如果值爲空,則行爲與調用clear_foo()相同。 -
string* release_foo()
:釋放字段的所有權並返回string對象的指針。調用此函數後,調用者將獲得所分配字符串對象的所有權,foo()
將返回空字符串/空字節。
單個枚舉字段(proto3)
enum Bar {
BAR_VALUE = 0;
OTHER_VALUE = 1;
}
定義:
Bar foo = 1;
編譯器將生成以下訪問器方法:
-
Bar foo() const
:返回字段的當前值。如果未設置字段,則返回默認值(0)。 -
void set_foo(Bar value)
:設置字段的值。調用此函數後,foo()
將返回值。 -
void clear_foo()
:清除字段值。調用此函數後,foo()
將返回默認值。
單個嵌入Message字段
給定message 定義
message Bar {}
對於這些字段定義中的任何一個:
//proto3
Bar foo = 1;
編譯器將生成以下訪問器方法:
-
bool has_foo() const
:如果設置了字段,則返回true。 -
const Bar& foo() const
:返回字段的當前值。如果未設置字段,則返回一個未設置任何字段的Bar
(可能爲Bar::default_instance()
)。 -
Bar* mutable_foo()
:返回存儲字段值的mutableBar
對象的指針。如果在調用之前未設置字段,則返回的欄將不設置任何字段(即,它將與新分配的Bar
相同)。調用此函數後,has_foo()
將返回true,foo()
將返回對同一個Bar
實例的引用。 -
void clear_foo()
:清除字段值。調用後,has_foo()
將返回false,foo()
將返回默認值。 -
void set_allocated_foo(Bar* bar)
:將Bar
對象設置爲字段,並釋放上一個字段值(如果存在)。如果Bar
指針不爲空,則消息將取得分配的Bar
對象的所有權,並且has_foo()
將返回true。否則,如果條爲空,則行爲與調用clear_foo()
相同。 -
Bar* release_foo()
:釋放字段的所有權並返回Bar
對象的指針。調用此函數後,調用者將獲得分配的Bar
對象的所有權,has_foo()
將返回false,foo()
將返回默認值。
Repeated 數字字段
對於此字段定義:
repeated int32 foo = 1;
編譯器將生成以下訪問器方法:
-
int foo_size() const
:返回字段中當前元素的數目。 -
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()
將返回零。 -
const RepeatedField<int32>& foo() const
:返回存儲字段元素的基礎RepeatedField
。這個容器類提供類似STL的迭代器和其他方法。 -
RepeatedField<int32>* mutable_foo()
:返回一個指向存儲字段元素的基礎mutableRepeatedField
的指針。這個容器類提供類似STL的迭代器和其他方法。
Repeated 字符串字段
對於這些字段定義之一:
repeated string foo = 1;
repeated bytes foo = 1;
編譯器將生成以下訪問器方法:
-
int foo_size() const
:返回字段中當前元素的數目。 -
const string& foo(int index) const
:返回給定索引處的元素。使用[0, foo_size())
之外的索引調用此方法會產生未定義的行爲。 -
void set_foo(int index, const string& value)
:在給定的從零開始的索引處設置元素的值。 -
void set_foo(int index, const char* value)
:使用C樣式的以空結尾的字符串在給定的從零開始的索引處設置元素的值。 -
void set_foo(int index, const char* value, int size)
:與上面類似,但字符串大小是顯式給定的,而不是通過查找空終止符字節來確定的。 -
string* mutable_foo(int index)
:返回一個指向mutablestring
對象的指針,該對象在給定的從零開始的索引處存儲元素的值。使用[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()
將返回零。 -
const RepeatedPtrField<string>& foo() const
:返回存儲字段元素的基礎RepeatedPtrField
。這個容器類提供類似STL的迭代器和其他方法。 -
RepeatedPtrField<string>* mutable_foo()
:返回一個指向存儲字段元素的基礎mutableRepeatedPtrField
的指針。這個容器類提供類似STL的迭代器和其他方法。
Repeated 枚舉字段
給定枚舉類型:
enum Bar {
BAR_VALUE = 0;
OTHER_VALUE = 1;
}
對於此字段定義:
repeated Bar foo = 1;
編譯器將生成以下訪問器方法:
-
int foo_size() const
:返回字段中當前元素的數目。 -
Bar foo(int index) const
:返回給定索引處的元素。使用[0, foo_size())
之外的索引調用此方法會產生未定義的行爲。 -
void set_foo(int index, Bar value)
:在給定的從零開始的索引處設置元素的值。在調試模式下(即未定義NDEBUG
),如果值與爲Bar
定義的任何值不匹配,則此方法將中止進程。 -
void add_foo(Bar value)
:將一個新元素附加到具有給定值的字段中。在調試模式下(即未定義NDEBUG
),如果值與爲Bar
定義的任何值不匹配,則此方法將中止進程。 -
void clear_foo()
:從字段中刪除所有元素。調用此函數後,foo_size()
將返回零。 -
const RepeatedField<int>& foo() const
:返回存儲字段元素的基礎RepeatedField
。這個容器類提供類似STL的迭代器和其他方法。 -
RepeatedField<int>* mutable_foo()
:返回一個指向存儲字段元素的基礎mutableRepeatedField
的指針。這個容器類提供類似STL的迭代器和其他方法。
Repeated嵌入的消息字段
給定消息類型:
message Bar {}
對於此字段定義:
repeated Bar foo = 1;
編譯器將生成以下訪問器方法:
-
int foo_size() const
:返回字段中當前元素的數目。 -
const Bar& foo(int index) const
:返回給定索引處的元素。使用[0, foo_size())
之外的索引調用此方法會產生未定義的行爲。 -
Bar* mutable_foo(int index)
:返回指向mutableBar
對象的指針,該對象在給定的基於零的索引處存儲元素的值。使用[0, foo_size())
之外的索引調用此方法會產生未定義的行爲。 -
Bar* add_foo()
:添加新元素並返回指向它的指針。返回的Bar
是可變的,不會設置任何字段(即,它將與新分配的條相同)。 -
void clear_foo()
:從字段中刪除所有元素。調用此函數後,foo_size()
將返回零。 -
const RepeatedPtrField<Bar>& foo() const
:返回存儲字段元素的基礎RepeatedPtrField
。這個容器類提供類似STL的迭代器和其他方法。 -
RepeatedPtrField<Bar>* mutable_foo()
:返回一個指向存儲字段元素的基礎mutableRepeatedPtrField
的指針。這個容器類提供類似STL的迭代器和其他方法。
Oneof 數字字段
對於此字段定義:
oneof oneof_name {
int32 foo = 1;
...
}
編譯器將生成以下訪問器方法:
-
int32 foo() const
:如果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.。
-
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
,並返回一個指向存儲字段值的可變字符串對象的指針。如果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
,則返回空值。 -
清除 oneof case,釋放字段的所有權並返回字符串對象的指針。調用此函數後,調用者將獲得分配字符串對象的所有權。
-
Oneof 枚舉字段
給定的枚舉類型:
enum Bar {
BAR_VALUE = 0;
OTHER_VALUE = 1;
}
對於此字段定義:
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
。 -
在調試模式下(即未定義
NDEBUG
),如果值與爲Bar
定義的任何值不匹配,則此方法將中止進程。
-
-
void clear_foo()
:-
如果oneof case 不是
kFoo
,則不會發生任何變化。 -
如果oneof case 是
kFoo
,則清除字段和kFoo
的值。
-
Oneof嵌入Message 字段
給定的枚舉類型:
message Bar {}
對於此字段定義:
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
對象的指針。如果其中一個事例在調用之前不是kFoo
,則返回的Bar
將不設置任何字段(即,它將與新分配的Bar
相同)。 -
調用後,
has_foo()
將返回true,foo()
將返回對同一個Bar
實例的引用,oneof_name_case()
將返回kFoo
。
-
-
void clear_foo()
:-
如果oneof case不是
kFoo
,則不會發生任何變化。 -
如果oneof case等於
kFoo
,則釋放字段並清除oneof case。has_foo()
將會返回否,foo()
將返回默認值並且oneof_name_case()
將返回ONEOF_NAME_NOT_SET
-
-
void set_allocated_foo(Bar* bar)
:-
調用
clear_oneof_name()
。 -
如果
Bar
指針不是空:將Bar
對象設置爲字段,並將oneof case設置爲kFoo
。消息擁有分配的Bar
對象的所有權,has_foo()
將返回true,oneof_name_case()
將返回kfoo。 -
如果指針爲空,
has_foo()
將返回false,oneof_name_case()
將返回ONEOF_NAME_NOT_SET
。(該行爲類似於調用clear_oneof_name()
)
-
-
Bar* release_foo()
:-
如果其中一個case不是
kFoo
,則返回空值。 -
如果oneof case是
kFoo
,則清除oneof case,釋放字段的所有權並返回Bar
對象的指針。調用此函數後,調用者將獲得分配的條對象的所有權,has_foo()
將返回false,foo()將返回默認值,oneof_name_case()
將返回ONEOF_NAME_NOT_SET
。
-
Map 字段
對於此字段定義:
map<int32, int32> weight = 1;
編譯器將生成以下訪問器方法:
-
const google::protobuf::Map<int32, int32>& weight();
:返回不可變映射。 -
google::protobuf::Map<int32, int32>* mutable_weight();
:返回可變映射。
google::protobuf::Map
是protocol buffer中用於存儲映射字段的特殊容器類型。從下面的接口可以看到,它使用了一個常用的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);
}
添加數據的最簡單方法是使用普通映射語法,例如:
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
相同的迭代器API。如果不想直接使用google::protobuf::Map
,可以通過執行以下操作將google::protobuf::Map
轉換爲標準map :
std::map<int32, int32> standard_map(message.weight().begin(),
message.weight().end());
請注意,這將對整個map進行深度複製。
還可以從標準map構造google::protobuf::Map
,如下所示:
google::protobuf::Map<int32, int32> weight(standard_map.begin(), standard_map.end());
Any
import "google/protobuf/any.proto";
message ErrorStatus {
string message = 1;
google.protobuf.Any details = 2;
}
在生成的代碼中,details
字段的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_name {
int32 foo_int = 4;
string foo_string = 9;
...
}
編譯器將生成以下訪問器方法:
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;
}
protocol buffer 編譯器將生成具有相同值集的稱爲Foo
的C++枚舉類型。此外,編譯器將生成以下函數:
-
const EnumDescriptor* Foo_descriptor()
:返回類型的描述符,其中包含有關此枚舉類型定義的值的信息。 -
bool Foo_IsValid(int value)
:如果給定的數值與Foo
定義的值之一匹配,則返回true。在上面的示例中,如果輸入爲0、5或1234,則返回true。 -
const string& Foo_Name(int value)
:返回給定數值的名稱。如果不存在此類值,則返回空字符串。如果多個值具有此數字,則返回定義的第一個值。在上面的示例中,Foo_Name(5)
將返回“VALUE_B
”。 -
bool Foo_Parse(const string& name, Foo* value)
:如果name
是此枚舉的有效值名稱,則將該值賦給value並返回true。否則返回false。在上面的示例中,Foo_Parse("VALUE_C", &some_foo
將返回true,並將some_foo
設置爲1234。 -
const Foo Foo_MIN
:枚舉的最小有效值(示例中的VALUE_A
)。 -
const Foo Foo_MAX
:枚舉的最大有效值(示例中的VALUE_C
)。 -
const int Foo_ARRAYSIZE
:始終定義爲Foo_MAX + 1
。
在switch語句中使用proto3枚舉時要小心。Proto3枚舉是開放的枚舉類型,可能值超出指定符號的範圍。解析proto3消息時將保留無法識別的枚舉值,並由枚舉字段訪問器返回。沒有默認大小寫的proto3枚舉上的switch語句將無法捕獲所有大小寫,即使列出了所有已知字段。這可能導致意外行爲,包括數據損壞和運行時崩潰。添加一個默認的大小寫,或者在開關外部顯式調用
Foo_IsValid
(int)來處理未知的枚舉值。
可以在消息類型內定義枚舉。在這種情況下,protocol buffer編譯器生成代碼,使其看起來枚舉類型本身被聲明爲嵌套在消息的類中。Foo_descriptor()
和Foo_IsValid()
函數聲明爲靜態方法。實際上,枚舉類型本身及其值是在全局作用域中聲明的,名稱不完整,並通過typedef和一系列常量定義導入到類的作用域中。這樣做只是爲了解決聲明排序的問題。
Arena Allocation
Arena Allocation是一個C++獨到的特性,它幫助您優化內存使用,並在使用協議緩衝區時提高性能。在.PROTO中啓用Arena Allocation,爲您的C++生成代碼添加了與arenas協同工作的附加代碼。
服務
如果.proto文件包含以下行:
option cc_generic_services = true;
然後protocol buffer編譯器將根據文件中的服務定義生成代碼。但是,所生成的代碼可能不受歡迎,因爲它沒有綁定到任何特定的RPC系統,因此需要更多級別的間接尋址,以便爲一個系統定製代碼。如果不希望生成此代碼,請將此行添加到文件:
option cc_generic_services = false;
如果上述兩行都沒有給出,則選項默認爲false,因爲通用服務已被棄用。
(請注意,在2.4.0之前,選項默認爲true)
基於.proto語言服務定義的RPC系統應該提供插件來生成適合系統的代碼。這些插件可能需要禁用抽象服務,以便它們可以生成自己的同名類。插件是2.3.0版(2010年1月)中的新插件。
本節的其餘部分描述啓用抽象服務時protocol buffer 編譯器生成的內容。
接口
給定服務定義:
service Foo {
rpc Bar(FooRequest) returns(FooResponse);
}
protocol buffer編譯器將生成一個類Foo
來表示此服務。Foo
將爲服務定義中定義的每個方法提供一個虛擬方法。在這種情況下,方法Bar
定義爲:
virtual void Bar(RpcController* controller, const FooRequest* request,
FooResponse* response, Closure* done);
這些參數等同於Service::CallMethod()
的參數,只是method
參數是隱含的,request
和response
指定了它們的確切類型。
這些生成的方法是虛擬的,但不是純虛擬的。默認實現只調用controller->SetFailed()
,並顯示一條錯誤消息,指示該方法未實現,然後調用done
回調。在實現自己的服務時,必須將生成的服務子類化,並根據需要實現其方法。
Foo
子類化服務接口。protocol buffer編譯器自動生成Service
方法的實現,如下所示:
-
GetDescriptor
:返回服務的ServiceDescriptor
。 -
CallMethod
:根據提供的方法描述符確定要調用的方法,並直接調用它,將請求和響應消息對象向下強制轉換爲正確的類型。 -
GetRequestPrototype
和GetResponsePrototype
:返回給定方法的正確類型的請求或響應的默認實例。
還生成以下靜態方法:
static ServiceDescriptor descriptor()
:返回類型的描述符,其中包含有關此服務具有哪些方法及其輸入和輸出類型的信息。
Stub
protocol buffer編譯器還生成每個服務接口的“stub”實現,希望向實現該服務的服務器發送請求的客戶機使用該實現。對於Foo
服務(上述),將定義存根實現Foo_Stub
。與嵌套消息類型一樣,使用typedef
,以便Foo_Stub
也可以稱爲Foo::Stub
。
Foo_Stub
是foo的一個子類,它還實現以下方法:
-
Foo_Stub(RpcChannel* channel)
:構造一個新的stub,它在給定的通道上發送請求。 -
Foo_Stub(RpcChannel* channel, ChannelOwnership ownership)
:構造一個新的stub,它在給定的通道上發送請求,並可能擁有該通道。如果ownership
爲Service::STUB_OWNS_CHANNEL
,那麼當stub對象被刪除時,它也將刪除該通道。 -
RpcChannel* channel()
:返回傳遞給構造函數的stub的通道。
stub還將每個服務的方法作爲通道的包裝器來實現。調用其中一個方法只需調用channel->CallMethod()
。
Protocol Buffer 庫不包括RPC實現。但是,它包含了將生成的服務類連接到您選擇的任意RPC實現所需的所有工具。只需要提供RpcChannel
和RpcController
的實現。
插件插入點
代碼生成器插件要擴展C++代碼生成器的輸出,可以使用給定的插入點名稱插入下列類型的代碼。除非另有說明,否則每個插入點都出現在.pb.cc文件和.pb.h文件中。
includes
:包括指令。
namespace_scope
:屬於文件包/名稱空間,但不屬於任何特定類的聲明。出現在所有其他命名空間範圍代碼之後。
global_scope
:屬於頂級的聲明,在文件的命名空間之外。出現在文件的最後。
class_scope:TYPENAME
:屬於消息類的成員聲明。TYPENAME
是完整的協議名,例如package.MessageType
。出現在類中所有其他公開聲明之後。這個插入點只出現在.pb.h文件中。
不要生成依賴於標準代碼生成器聲明的私有類成員的代碼,因爲這些實現細節可能在未來的Protocol Buffer版本中發生更改。
歡迎關注我的公衆號,持續分析優質技術文章