Proto3:C++代碼生成指南


本章節實際上是介紹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);

更多信息,參見這里


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM