Protobuf: C++ 產生的代碼簡析(Proto3)

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.solibprotobuf.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 { } }

在這種情況下,編譯器生成兩個類:FooFoo_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():返回存儲字段值的mutable Bar對象的指針。如果在調用之前未設置字段,則返回的欄將不設置任何字段(即,它將與新分配的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():返回一個指向存儲字段元素的基礎mutable RepeatedField的指針。這個容器類提供類似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):返回一個指向mutable 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()將返回零。

  • const RepeatedPtrField<string>& foo() const:返回存儲字段元素的基礎RepeatedPtrField。這個容器類提供類似STL的迭代器和其他方法。

  • RepeatedPtrField<string>* mutable_foo():返回一個指向存儲字段元素的基礎mutable RepeatedPtrField的指針。這個容器類提供類似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():返回一個指向存儲字段元素的基礎mutable RepeatedField的指針。這個容器類提供類似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):返回指向mutable Bar對象的指針,該對象在給定的基於零的索引處存儲元素的值。使用 [0, foo_size()) 之外的索引調用此方法會產生未定義的行爲。

  • Bar* add_foo():添加新元素並返回指向它的指針。返回的Bar是可變的,不會設置任何字段(即,它將與新分配的條相同)。

  • void clear_foo():從字段中刪除所有元素。調用此函數後,foo_size()將返回零。

  • const RepeatedPtrField<Bar>& foo() const:返回存儲字段元素的基礎 RepeatedPtrField 。這個容器類提供類似STL的迭代器和其他方法。

  • RepeatedPtrField<Bar>* mutable_foo():返回一個指向存儲字段元素的基礎mutable RepeatedPtrField 的指針。這個容器類提供類似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::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);
}

添加數據的最簡單方法是使用普通映射語法,例如:

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相同的迭代器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參數是隱含的,requestresponse指定了它們的確切類型。

這些生成的方法是虛擬的,但不是純虛擬的。默認實現只調用controller->SetFailed(),並顯示一條錯誤消息,指示該方法未實現,然後調用done回調。在實現自己的服務時,必須將生成的服務子類化,並根據需要實現其方法。

Foo子類化服務接口。protocol buffer編譯器自動生成Service方法的實現,如下所示:

  • GetDescriptor:返回服務的ServiceDescriptor

  • CallMethod:根據提供的方法描述符確定要調用的方法,並直接調用它,將請求和響應消息對象向下強制轉換爲正確的類型。

  • GetRequestPrototypeGetResponsePrototype:返回給定方法的正確類型的請求或響應的默認實例。

還生成以下靜態方法:

  • 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,它在給定的通道上發送請求,並可能擁有該通道。如果ownershipService::STUB_OWNS_CHANNEL,那麼當stub對象被刪除時,它也將刪除該通道。

  • RpcChannel* channel():返回傳遞給構造函數的stub的通道。

stub還將每個服務的方法作爲通道的包裝器來實現。調用其中一個方法只需調用channel->CallMethod()

Protocol Buffer 庫不包括RPC實現。但是,它包含了將生成的服務類連接到您選擇的任意RPC實現所需的所有工具。只需要提供RpcChannelRpcController的實現。

插件插入點

代碼生成器插件要擴展C++代碼生成器的輸出,可以使用給定的插入點名稱插入下列類型的代碼。除非另有說明,否則每個插入點都出現在.pb.cc文件和.pb.h文件中。

includes:包括指令。

namespace_scope:屬於文件包/名稱空間,但不屬於任何特定類的聲明。出現在所有其他命名空間範圍代碼之後。

global_scope:屬於頂級的聲明,在文件的命名空間之外。出現在文件的最後。

class_scope:TYPENAME:屬於消息類的成員聲明。TYPENAME是完整的協議名,例如package.MessageType。出現在類中所有其他公開聲明之後。這個插入點只出現在.pb.h文件中。

不要生成依賴於標準代碼生成器聲明的私有類成員的代碼,因爲這些實現細節可能在未來的Protocol Buffer版本中發生更改。

歡迎關注我的公衆號,持續分析優質技術文章
歡迎關注我的公衆號

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章