C++ 程式碼產生指南

說明 protocol buffer 編譯器針對任何給定的協定定義,會產生哪些 C++ 程式碼。

proto2 和 proto3 產生程式碼之間的任何差異都會以醒目方式標示 - 請注意,這些差異在本文件中描述的產生程式碼中,而不是在基礎訊息類別/介面中,基礎訊息類別/介面在這兩個版本中是相同的。在閱讀本文檔之前,您應該先閱讀 proto2 語言指南和/或 proto3 語言指南

編譯器調用

當使用 --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,並產生四個輸出檔案: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 宣告

package foo.bar;

檔案中的所有宣告都會位於 foo::bar 命名空間中。

訊息

給定一個簡單的訊息宣告

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 介面,該介面僅包含 Message 方法的子集。特別是,它不支援描述元或反射。但是,在此模式下,產生的程式碼只需要連結到 libprotobuf-lite.so (Windows 上為 libprotobuf-lite.lib),而不是 libprotobuf.so (libprotobuf.lib)。「輕量」程式庫比完整程式庫小得多,更適合資源受限的系統,例如手機。

不應建立自己的 Foo 子類別。如果您將此類別子類別化並覆寫虛擬方法,則覆寫可能會被忽略,因為許多產生的方法呼叫都會被取消虛擬化以提高效能。

Message 介面定義了可讓您檢查、操作、讀取或寫入整個訊息的方法,包括從二進位字串剖析和序列化為二進位字串。

  • bool ParseFromString(::absl::string_view data):從給定的序列化二進位字串 (也稱為線路格式) 剖析訊息。
  • bool SerializeToString(string* output) const:將給定的訊息序列化為二進位字串。
  • string DebugString():傳回一個字串,其中包含 proto 的 text_format 表示法 (僅應用於除錯)。

除了這些方法之外,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():傳回類型的描述元。其中包含有關類型的資訊,包括它有哪些欄位以及這些欄位的類型。這可以與 反射 一起使用,以程式化方式檢查欄位。
  • static const Foo& default_instance():傳回 Foo 的 const 單例實例,它與新建構的 Foo 實例相同 (因此所有單數欄位都未設定,且所有重複欄位都為空)。請注意,訊息的預設實例可以透過呼叫其 New() 方法用作工廠。

產生的檔案名稱

保留關鍵字 會在產生的輸出中附加底線。

例如,以下 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);

巢狀類型

訊息可以在另一個訊息內宣告。例如

message Foo {
  message Bar {}
}

在此情況下,編譯器會產生兩個類別:FooFoo_Bar。此外,編譯器會在 Foo 內產生 typedef,如下所示

typedef Foo_Bar Bar;

這表示您可以像使用巢狀類別 Foo::Bar 一樣使用巢狀類型的類別。但是,請注意 C++ 不允許巢狀類型被向前宣告。如果您想在另一個檔案中向前宣告 Bar 並使用該宣告,則必須將其識別為 Foo_Bar

欄位

除了上一節中描述的方法之外,protocol buffer 編譯器還會為 .proto 檔案中訊息內定義的每個欄位產生一組存取器方法。這些方法採用小寫/蛇紋命名法,例如 has_foo()clear_foo()

除了存取器方法之外,編譯器還會為每個包含其欄位編號的欄位產生一個整數常數。常數名稱為字母 k,後跟轉換為駝峰式大小寫的欄位名稱,然後是 FieldNumber。例如,給定欄位 optional int32 foo_bar = 5;,編譯器將產生常數 static const int kFooBarFieldNumber = 5;

對於傳回 const 參考的欄位存取器,當對訊息進行下一個修改存取時,該參考可能會失效。這包括呼叫任何欄位的任何非 const 存取器、呼叫從 Message 繼承的任何非 const 方法,或透過其他方式修改訊息 (例如,透過使用訊息作為 Swap() 的引數)。對應地,只有在同時未對訊息進行任何修改存取的情況下,才能保證傳回參考的位址在存取器的不同調用中保持相同。

對於傳回指標的欄位存取器,當對訊息進行下一個修改或非修改存取時,該指標可能會失效。這包括 (無論是否為 const) 呼叫任何欄位的任何存取器、呼叫從 Message 繼承的任何方法,或透過其他方式存取訊息 (例如,透過使用複製建構函式複製訊息)。對應地,永遠無法保證傳回指標的值在存取器的兩個不同調用中保持相同。

選填數值欄位 (proto2 和 proto3)

對於這些欄位定義中的任一個

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() 將傳回 value
  • void clear_foo():清除欄位的值。在呼叫此方法之後,has_foo() 將傳回 false,而 foo() 將傳回預設值。

對於其他數值欄位類型 (包括 bool),int32 會根據 純量值類型表 替換為對應的 C++ 類型。

隱含存在性數值欄位 (proto3)

對於以下欄位定義

int32 foo = 1;  // no field label specified, defaults to implicit presence.

編譯器將產生以下存取器方法

  • int32 foo() const:傳回欄位的目前值。如果欄位未設定,則傳回 0。
  • void set_foo(int32 value):設定欄位的值。在呼叫此方法之後,foo() 將傳回 value
  • void clear_foo():清除欄位的值。在呼叫此方法之後,foo() 將傳回 0。

對於其他數值欄位類型 (包括 bool),int32 會根據 純量值類型表 替換為對應的 C++ 類型。

選填字串/位元組欄位 (proto2 和 proto3)

注意: 從 2023 版本起,如果 features.(pb.cpp).string_type 設定為 VIEW,則會改為產生 string_view API。

對於這些欄位定義中的任一個

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(::absl::string_view value):設定欄位的值。在呼叫此方法之後,has_foo() 將傳回 true,而 foo() 將傳回 value 的副本。
  • void set_foo(const string& value):設定欄位的值。在呼叫此方法之後,has_foo() 將傳回 true,而 foo() 將傳回 value 的副本。
  • void set_foo(string&& value):設定欄位的值,從傳遞的字串移動。在呼叫此方法之後,has_foo() 將傳回 true,而 foo() 將傳回 value 的副本。
  • void set_foo(const char* value):使用 C 樣式 Null 終止字串設定欄位的值。在呼叫此方法之後,has_foo() 將傳回 true,而 foo() 將傳回 value 的副本。
  • void set_foo(const char* value, int size):使用具有明確指定大小的字串 (而不是透過尋找 Null 終止位元組來判斷) 設定欄位的值。在呼叫此方法之後,has_foo() 將傳回 true,而 foo() 將傳回 value 的副本。
  • string* mutable_foo():傳回儲存欄位值的可變 string 物件的指標。如果在呼叫之前未設定欄位,則傳回的字串將為空 (不是預設值)。在呼叫此方法之後,has_foo() 將傳回 true,而 foo() 將傳回寫入給定字串的任何值。
  • void clear_foo():清除欄位的值。在呼叫此方法之後,has_foo() 將傳回 false,而 foo() 將傳回預設值。
  • void set_allocated_foo(string* value):將 string 物件設定為欄位,並釋放先前的欄位值 (如果存在)。如果 string 指標不是 NULL,則訊息會取得已配置 string 物件的所有權,且 has_foo() 將傳回 true。訊息可以隨時刪除已配置的 string 物件,因此對物件的參考可能會失效。否則,如果 valueNULL,則行為與呼叫 clear_foo() 相同。
  • string* release_foo():釋放欄位的所有權,並傳回 string 物件的指標。在呼叫此方法之後,呼叫者會取得已配置 string 物件的所有權,has_foo() 將傳回 false,而 foo() 將傳回預設值。

隱含存在性字串/位元組欄位 (proto3)

注意: 從 2023 版本起,如果 features.(pb.cpp).string_type 設定為 VIEW,則會改為產生 string_view API。

對於這些欄位定義中的任一個

string foo = 1;  // no field label specified, defaults to implicit presence.
bytes foo = 1;

編譯器將產生以下存取器方法

  • const string& foo() const:傳回欄位的目前值。如果欄位未設定,則傳回空字串/空位元組。
  • void set_foo(::absl::string_view value):設定欄位的值。在呼叫此方法之後,foo() 將傳回 value 的副本。
  • void set_foo(const string& value):設定欄位的值。在呼叫此方法之後,foo() 將傳回 value 的副本。
  • void set_foo(string&& value):設定欄位的值,從傳遞的字串移動。在呼叫此方法之後,foo() 將傳回 value 的副本。
  • void set_foo(const char* value):使用 C 樣式 Null 終止字串設定欄位的值。在呼叫此方法之後,foo() 將傳回 value 的副本。
  • void set_foo(const char* value, int size):使用具有明確指定大小的字串 (而不是透過尋找 Null 終止位元組來判斷) 設定欄位的值。在呼叫此方法之後,foo() 將傳回 value 的副本。
  • string* mutable_foo():傳回儲存欄位值的可變 string 物件的指標。如果在呼叫之前未設定欄位,則傳回的字串將為空。在呼叫此方法之後,foo() 將傳回寫入給定字串的任何值。
  • void clear_foo():清除欄位的值。在呼叫此方法之後,foo() 將傳回空字串/空位元組。
  • void set_allocated_foo(string* value):將 string 物件設定為欄位,並釋放先前的欄位值 (如果存在)。如果 string 指標不是 NULL,則訊息會取得已配置 string 物件的所有權。訊息可以隨時刪除已配置的 string 物件,因此對物件的參考可能會失效。否則,如果 valueNULL,則行為與呼叫 clear_foo() 相同。
  • string* release_foo():釋放欄位的所有權,並傳回 string 物件的指標。在呼叫此方法之後,呼叫者會取得已配置 string 物件的所有權,而 foo() 將傳回空字串/空位元組。

支援 Cord 的單數字元組欄位

v23.0 新增了對單數 bytes 欄位 (oneof 欄位) 的 absl::Cord 支援。單數 stringrepeated stringrepeated bytes 欄位不支援使用 Cord

若要設定單數 bytes 欄位以使用 absl::Cord 儲存資料,請使用以下語法

optional bytes foo = 25 [ctype=CORD];
bytes bar = 26 [ctype=CORD];

cord 不適用於 repeated bytes 欄位。Protoc 會忽略這些欄位上的 [ctype=CORD] 設定。

編譯器將產生以下存取器方法

  • const ::absl::Cord& foo() const:傳回欄位的目前值。如果欄位未設定,則傳回空的 Cord (proto3) 或預設值 (proto2)。
  • void set_foo(const ::absl::Cord& value):設定欄位的值。在呼叫此方法之後,foo() 將傳回 value
  • void set_foo(::absl::string_view value):設定欄位的值。在呼叫此方法之後,foo() 將以 absl::Cord 形式傳回 value
  • void clear_foo():清除欄位的值。在呼叫此方法之後,foo() 將傳回空的 Cord (proto3) 或預設值 (proto2)。
  • bool has_foo():如果欄位已設定,則傳回 true

選填列舉欄位 (proto2 和 proto3)

給定列舉類型

enum Bar {
  BAR_UNSPECIFIED = 0;
  BAR_VALUE = 1;
  BAR_OTHER_VALUE = 2;
}

對於這些欄位定義中的任一個

optional Bar bar = 1;
required Bar bar = 1;

編譯器將產生以下存取器方法

  • bool has_bar() const:如果欄位已設定,則傳回 true
  • Bar bar() const:傳回欄位的目前值。如果欄位未設定,則傳回預設值。
  • void set_bar(Bar value):設定欄位的值。在呼叫此方法之後,has_bar() 將傳回 true,而 bar() 將傳回 value。在除錯模式下 (即未定義 NDEBUG),如果 value 與為 Bar 定義的任何值不符,此方法將中止程序。
  • void clear_bar():清除欄位的值。在呼叫此方法之後,has_bar() 將傳回 false,而 bar() 將傳回預設值。

隱含存在性列舉欄位 (proto3)

給定列舉類型

enum Bar {
  BAR_UNSPECIFIED = 0;
  BAR_VALUE = 1;
  BAR_OTHER_VALUE = 2;
}

對於此欄位定義

Bar bar = 1;  // no field label specified, defaults to implicit presence.

編譯器將產生以下存取器方法

  • Bar bar() const:傳回欄位的目前值。如果欄位未設定,則傳回預設值 (0)。
  • void set_bar(Bar value):設定欄位的值。在呼叫此方法之後,bar() 將傳回 value
  • void clear_bar():清除欄位的值。在呼叫此方法之後,bar() 將傳回預設值。

選填嵌入訊息欄位 (proto2 和 proto3)

給定訊息類型

message Bar {}

對於這些欄位定義中的任一個

//proto2
optional Bar bar = 1;
required Bar bar = 1;

//proto3
Bar bar = 1;

編譯器將產生以下存取器方法

  • bool has_bar() const:如果欄位已設定,則傳回 true
  • const Bar& bar() const:傳回欄位的目前值。如果欄位未設定,則傳回 Bar,且其任何欄位都未設定 (可能是 Bar::default_instance())。
  • Bar* mutable_bar():傳回儲存欄位值的可變 Bar 物件的指標。如果在呼叫之前未設定欄位,則傳回的 Bar 將不會設定任何欄位 (即,它將與新配置的 Bar 相同)。在呼叫此方法之後,has_bar() 將傳回 true,而 bar() 將傳回對同一個 Bar 實例的參考。
  • void clear_bar():清除欄位的值。在呼叫此方法之後,has_bar() 將傳回 false,而 bar() 將傳回預設值。
  • void set_allocated_bar(Bar* bar):將 Bar 物件設定為欄位,並釋放先前的欄位值 (如果存在)。如果 Bar 指標不是 NULL,則訊息會取得已配置 Bar 物件的所有權,且 has_bar() 將傳回 true。否則,如果 BarNULL,則行為與呼叫 clear_bar() 相同。
  • Bar* release_bar():釋放欄位的所有權,並傳回 Bar 物件的指標。在呼叫此方法之後,呼叫者會取得已配置 Bar 物件的所有權,has_bar() 將傳回 false,而 bar() 將傳回預設值。

重複數值欄位

對於此欄位定義

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() 將傳回零。
  • const RepeatedField<int32>& foo() const:傳回儲存欄位元素的基础 RepeatedField。此容器類別提供類似 STL 的迭代器和其他方法。
  • RepeatedField<int32>* mutable_foo():傳回儲存欄位元素的基础可變 RepeatedField 的指標。此容器類別提供類似 STL 的迭代器和其他方法。

對於其他數值欄位類型 (包括 bool),int32 會根據 純量值類型表 替換為對應的 C++ 類型。

重複字串欄位

注意: 從 2023 版本起,如果 features.(pb.cpp).string_type 設定為 VIEW,則會改為產生 string_view API。

對於這些欄位定義中的任一個

repeated string foo = 1;
repeated bytes foo = 1;

編譯器將產生以下存取器方法

  • int foo_size() const:傳回欄位中目前的元素數量。若要檢查是否為空集合,請考慮使用基礎 RepeatedField 中的 empty() 方法,而不是此方法。
  • const string& foo(int index) const:傳回給定從零開始的索引處的元素。使用超出 [0, foo_size()-1] 範圍的索引呼叫此方法會產生未定義的行為。
  • void set_foo(int index, ::absl::string_view value):設定給定從零開始的索引處的元素值。
  • void set_foo(int index, const string& value):設定給定從零開始的索引處的元素值。
  • void set_foo(int index, string&& value):設定給定從零開始的索引處的元素值,從傳遞的字串移動。
  • void set_foo(int index, const char* value):使用 C 樣式 Null 終止字串設定給定從零開始的索引處的元素值。
  • void set_foo(int index, const char* value, int size):使用具有明確指定大小的 C 樣式字串 (而不是透過尋找 Null 終止位元組來判斷) 設定給定從零開始的索引處的元素值。
  • string* mutable_foo(int index):傳回儲存給定從零開始的索引處的元素值的可變 string 物件的指標。使用超出 [0, foo_size()) 範圍的索引呼叫此方法會產生未定義的行為。
  • void add_foo(::absl::string_view value):將具有給定值的新元素附加到欄位的末尾。
  • void add_foo(const string& value):將具有給定值的新元素附加到欄位的末尾。
  • void add_foo(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():從欄位中移除所有元素。在呼叫此方法之後,foo_size() 將傳回零。
  • const RepeatedPtrField<string>& foo() const:傳回儲存欄位元素的基础 RepeatedPtrField。此容器類別提供類似 STL 的迭代器和其他方法。
  • RepeatedPtrField<string>* mutable_foo():傳回儲存欄位元素的基础可變 RepeatedPtrField 的指標。此容器類別提供類似 STL 的迭代器和其他方法。

重複列舉欄位

給定列舉類型

enum Bar {
  BAR_UNSPECIFIED = 0;
  BAR_VALUE = 1;
  BAR_OTHER_VALUE = 2;
}

對於此欄位定義

repeated Bar bar = 1;

編譯器將產生以下存取器方法

  • int bar_size() const:傳回欄位中目前的元素數量。若要檢查是否為空集合,請考慮使用基礎 RepeatedField 中的 empty() 方法,而不是此方法。
  • Bar bar(int index) const:傳回給定從零開始的索引處的元素。使用超出 [0, bar_size()) 範圍的索引呼叫此方法會產生未定義的行為。
  • void set_bar(int index, Bar value):設定給定從零開始的索引處的元素值。在除錯模式下 (即未定義 NDEBUG),如果 value 與為 Bar 定義的任何值不符,此方法將中止程序。
  • void add_bar(Bar value):將具有給定值的新元素附加到欄位的末尾。在除錯模式下 (即未定義 NDEBUG),如果 value 與為 Bar 定義的任何值不符,此方法將中止程序。
  • void clear_bar():從欄位中移除所有元素。在呼叫此方法之後,bar_size() 將傳回零。
  • const RepeatedField<int>& bar() const:傳回儲存欄位元素的基础 RepeatedField。此容器類別提供類似 STL 的迭代器和其他方法。
  • RepeatedField<int>* mutable_bar():傳回儲存欄位元素的基础可變 RepeatedField 的指標。此容器類別提供類似 STL 的迭代器和其他方法。

重複嵌入訊息欄位

給定訊息類型

message Bar {}

對於此欄位定義

repeated Bar bar = 1;

編譯器將產生以下存取器方法

  • int bar_size() const:傳回欄位中目前的元素數量。若要檢查是否為空集合,請考慮使用基礎 RepeatedField 中的 empty() 方法,而不是此方法。
  • const Bar& bar(int index) const:傳回給定從零開始的索引處的元素。使用超出 [0, bar_size()) 範圍的索引呼叫此方法會產生未定義的行為。
  • Bar* mutable_bar(int index):傳回儲存給定從零開始的索引處的元素值的可變 Bar 物件的指標。使用超出 [0, bar_size()) 範圍的索引呼叫此方法會產生未定義的行為。
  • Bar* add_bar():將新元素新增至欄位的末尾,並傳回指向它的指標。傳回的 Bar 是可變的,且不會設定任何欄位 (即,它將與新配置的 Bar 相同)。
  • void clear_bar():從欄位中移除所有元素。在呼叫此方法之後,bar_size() 將傳回零。
  • const RepeatedPtrField<Bar>& bar() const:傳回儲存欄位元素的基础 RepeatedPtrField。此容器類別提供類似 STL 的迭代器和其他方法。
  • RepeatedPtrField<Bar>* mutable_bar():傳回儲存欄位元素的基础可變 RepeatedPtrField 的指標。此容器類別提供類似 STL 的迭代器和其他方法。

Oneof 數值欄位

對於此 oneof 欄位定義

oneof example_name {
    int32 foo = 1;
    ...
}

編譯器將產生以下存取器方法

  • bool has_foo() const:如果 oneof case 為 kFoo,則傳回 true
  • int32 foo() const:如果 oneof case 為 kFoo,則傳回欄位的目前值。否則,傳回預設值。
  • void set_foo(int32 value):
    • 如果設定了同一個 oneof 中的任何其他 oneof 欄位,則呼叫 clear_example_name()
    • 設定此欄位的值,並將 oneof case 設定為 kFoo
    • has_foo() 將傳回 true,foo() 將傳回 value,而 example_name_case() 將傳回 kFoo
  • void clear_foo():
    • 如果 oneof case 不是 kFoo,則不會進行任何變更。
    • 如果 oneof case 為 kFoo,則清除欄位的值和 oneof case。has_foo() 將傳回 falsefoo() 將傳回預設值,而 example_name_case() 將傳回 EXAMPLE_NAME_NOT_SET

對於其他數值欄位類型 (包括 bool),int32 會根據 純量值類型表 替換為對應的 C++ 類型。

Oneof 字串欄位

注意: 從 2023 版本起,可能會改為產生 string_view API

對於這些 oneof 欄位定義中的任一個

oneof example_name {
    string foo = 1;
    ...
}
oneof example_name {
    bytes foo = 1;
    ...
}

編譯器將產生以下存取器方法

  • bool has_foo() const:如果 oneof case 為 kFoo,則傳回 true
  • const string& foo() const:如果 oneof 案例為 kFoo,則傳回欄位的目前值。否則,傳回預設值。
  • void set_foo(::absl::string_view value):
    • 如果設定了同一個 oneof 中的任何其他 oneof 欄位,則呼叫 clear_example_name()
    • 設定此欄位的值,並將 oneof case 設定為 kFoo
    • has_foo() 將傳回 truefoo() 將傳回 value 的副本,而 example_name_case() 將傳回 kFoo
  • void set_foo(const string& value):與第一個 set_foo() 類似,但從 const string 參考複製。
  • void set_foo(string&& value):與第一個 set_foo() 類似,但從傳入的 string 移動。
  • void set_foo(const char* value):與第一個 set_foo() 類似,但從 C 風格的 null 終止字串複製。
  • void set_foo(const char* value, int size):與第一個 set_foo() 類似,但從具有明確指定大小(而非透過尋找 null 終止位元組判斷)的字串複製。
  • string* mutable_foo():
    • 如果設定了同一個 oneof 中的任何其他 oneof 欄位,則呼叫 clear_example_name()
    • 將 oneof 案例設定為 kFoo,並傳回儲存欄位值的可變字串物件的指標。如果呼叫前 oneof 案例不是 kFoo,則傳回的字串將為空(而非預設值)。
    • has_foo() 將傳回 truefoo() 將傳回寫入給定字串的任何值,而 example_name_case() 將傳回 kFoo
  • void clear_foo():
    • 如果 oneof 案例不是 kFoo,則不會有任何變更。
    • 如果 oneof 案例是 kFoo,則釋放欄位並清除 oneof 案例。has_foo() 將傳回 falsefoo() 將傳回預設值,而 example_name_case() 將傳回 EXAMPLE_NAME_NOT_SET
  • void set_allocated_foo(string* value):
    • 呼叫 clear_example_name()
    • 如果字串指標不是 NULL:將字串物件設定為欄位,並將 oneof 案例設定為 kFoo。訊息取得已配置字串物件的所有權,has_foo() 將傳回 true,而 example_name_case() 將傳回 kFoo
    • 如果字串指標為 NULLhas_foo() 將傳回 false,而 example_name_case() 將傳回 EXAMPLE_NAME_NOT_SET
  • string* release_foo():
    • 如果 oneof 案例不是 kFoo,則傳回 NULL
    • 清除 oneof 案例,釋放欄位的所有權,並傳回字串物件的指標。呼叫此方法後,呼叫者取得已配置字串物件的所有權,has_foo() 將傳回 false,foo() 將傳回預設值,而 example_name_case() 將傳回 EXAMPLE_NAME_NOT_SET

Oneof 列舉欄位

給定列舉類型

enum Bar {
  BAR_UNSPECIFIED = 0;
  BAR_VALUE = 1;
  BAR_OTHER_VALUE = 2;
}

對於 oneof 欄位定義

oneof example_name {
    Bar bar = 1;
    ...
}

編譯器將產生以下存取器方法

  • bool has_bar() const:如果 oneof 案例為 kBar,則傳回 true
  • Bar bar() const:如果 oneof 案例為 kBar,則傳回欄位的目前值。否則,傳回預設值。
  • void set_bar(Bar value):
    • 如果設定了同一個 oneof 中的任何其他 oneof 欄位,則呼叫 clear_example_name()
    • 設定此欄位的值,並將 oneof 案例設定為 kBar
    • has_bar() 將傳回 truebar() 將傳回 value,而 example_name_case() 將傳回 kBar
    • 在偵錯模式(即未定義 NDEBUG)中,如果 value 與為 Bar 定義的任何值都不符,則此方法將中止程序。
  • void clear_bar():
    • 如果 oneof 案例不是 kBar,則不會有任何變更。
    • 如果 oneof 案例是 kBar,則清除欄位的值和 oneof 案例。has_bar() 將傳回 falsebar() 將傳回預設值,而 example_name_case() 將傳回 EXAMPLE_NAME_NOT_SET

Oneof 嵌入訊息欄位

給定訊息類型

message Bar {}

對於 oneof 欄位定義

oneof example_name {
    Bar bar = 1;
    ...
}

編譯器將產生以下存取器方法

  • bool has_bar() const:如果 oneof 案例為 kBar,則傳回 true。
  • const Bar& bar() const:如果 oneof 案例為 kBar,則傳回欄位的目前值。否則,傳回一個未設定任何欄位的 Bar(可能是 Bar::default_instance())。
  • Bar* mutable_bar():
    • 如果設定了同一個 oneof 中的任何其他 oneof 欄位,則呼叫 clear_example_name()
    • 將 oneof 案例設定為 kBar,並傳回儲存欄位值的可變 Bar 物件的指標。如果呼叫前 oneof 案例不是 kBar,則傳回的 Bar 將未設定任何欄位(即,它將與新配置的 Bar 相同)。
    • 呼叫此方法後,has_bar() 將傳回 truebar() 將傳回對 Bar 的相同執行個體的參考,而 example_name_case() 將傳回 kBar
  • void clear_bar():
    • 如果 oneof 案例不是 kBar,則不會有任何變更。
    • 如果 oneof 案例等於 kBar,則釋放欄位並清除 oneof 案例。has_bar() 將傳回 falsebar() 將傳回預設值,而 example_name_case() 將傳回 EXAMPLE_NAME_NOT_SET
  • void set_allocated_bar(Bar* bar):
    • 呼叫 clear_example_name()
    • 如果 Bar 指標不是 NULL:將 Bar 物件設定為欄位,並將 oneof 案例設定為 kBar。訊息取得已配置 Bar 物件的所有權,has_bar() 將傳回 true,而 example_name_case() 將傳回 kBar
    • 如果指標為 NULLhas_bar() 將傳回 false,而 example_name_case() 將傳回 EXAMPLE_NAME_NOT_SET。(行為類似於呼叫 clear_example_name()
  • Bar* release_bar():
    • 如果 oneof 案例不是 kBar,則傳回 NULL
    • 如果 oneof 案例是 kBar,則清除 oneof 案例,釋放欄位的所有權,並傳回 Bar 物件的指標。呼叫此方法後,呼叫者取得已配置 Bar 物件的所有權,has_bar() 將傳回 falsebar() 將傳回預設值,而 example_name_case() 將傳回 EXAMPLE_NAME_NOT_SET

Map 欄位

對於此 map 欄位定義

map<int32, int32> weight = 1;

編譯器將產生以下存取器方法

  • const google::protobuf::Map<int32, int32>& weight();:傳回不可變的 Map
  • google::protobuf::Map<int32, int32>* mutable_weight();:傳回可變的 Map

google::protobuf::Map 是在 Protocol Buffers 中使用的特殊容器類型,用於儲存 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
  bool contains(const Key& key) const;
  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;

google::protobuf::Map 與標準 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());

剖析不明值

在傳輸線路上,.proto map 相當於每個鍵/值對的 map 項目訊息,而 map 本身是 map 項目的重複欄位。與一般的訊息類型一樣,剖析的 map 項目訊息可能具有不明欄位:例如,在定義為 map<int32, string> 的 map 中的 int64 類型欄位。

如果 map 項目訊息的傳輸線路格式中有不明欄位,則會捨棄這些欄位。

如果 map 項目訊息的傳輸線路格式中有不明的列舉值,則在 proto2 和 proto3 中的處理方式有所不同。在 proto2 中,整個 map 項目訊息會放入包含訊息的不明欄位集中。在 proto3 中,它會放入 map 欄位中,就像它是已知的列舉值一樣。

Any

給定一個 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”. Returns false if serializing the message failed.
  bool PackFrom(const google::protobuf::Message& message);

  // Packs the given message into this Any using the given type URL
  // prefix. Returns false if serializing the message failed.
  bool PackFrom(const google::protobuf::Message& message,
                ::absl::string_view 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 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

列舉

注意: 自 2024 年版起,可能會使用某些功能設定產生 string_view API。如需更多資訊,請參閱 列舉名稱協助程式 這個主題。

給定如下所示的列舉定義

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(::absl::string_view 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

將整數轉換為 proto2 列舉時請小心。 如果將整數轉換為 proto2 列舉值,則整數必須是該列舉的有效值之一,否則結果可能未定義。如有疑問,請使用產生的 Foo_IsValid() 函式來測試轉換是否有效。將 proto2 訊息的列舉類型欄位設定為無效值可能會導致判斷提示失敗。如果在剖析 proto2 訊息時讀取到無效的列舉值,則會將其視為不明欄位。這些語意已在 proto3 中變更。只要整數符合 int32,就可以安全地將任何整數轉換為 proto3 列舉值。剖析 proto3 訊息時也會保留無效的列舉值,並由列舉欄位存取子傳回。

在 switch 陳述式中使用 proto3 列舉時請小心。 Proto3 列舉是開放式列舉類型,其可能的值超出指定符號的範圍。剖析 proto3 訊息時會保留無法辨識的列舉值,並由列舉欄位存取子傳回。即使列出了所有已知的欄位,在沒有預設案例的情況下,針對 proto3 列舉的 switch 陳述式也無法捕捉所有案例。這可能會導致非預期的行為,包括資料損毀和執行階段當機。務必新增預設案例,或明確呼叫 switch 外部的 Foo_IsValid(int) 以處理不明的列舉值。

您可以在訊息類型內定義列舉。在這種情況下,Protocol Buffer 編譯器產生的程式碼會使其看起來像是列舉類型本身是在訊息的類別內部宣告的。Foo_descriptor()Foo_IsValid() 函式會宣告為靜態方法。實際上,列舉類型本身及其值是在全域範圍中以混雜名稱宣告的,並透過 typedef 和一系列常數定義匯入類別的範圍中。這樣做只是為了繞過宣告順序的問題。請勿依賴混雜的頂層名稱;假裝列舉確實巢狀於訊息類別中。

擴充 (僅限 proto2)

給定具有擴充範圍的訊息

message Foo {
  extensions 100 to 199;
}

Protocol Buffer 編譯器將為 Foo 產生一些額外的方法:HasExtension()ExtensionSize()ClearExtension()GetExtension()SetExtension()MutableExtension()AddExtension()SetAllocatedExtension()ReleaseExtension()。這些方法中的每一個都將擴充識別碼(如下所述)作為其第一個參數,該識別碼識別擴充欄位。其餘參數和傳回值與針對與擴充識別碼相同類型的正常(非擴充)欄位產生的對應存取子方法完全相同。(GetExtension() 對應於沒有特殊前置詞的存取子。)

給定擴充定義

extend Foo {
  optional int32 bar = 123;
  repeated int32 repeated_bar = 124;
  optional Bar message_bar = 125;
}

對於單數擴充欄位 bar,Protocol Buffer 編譯器會產生一個名為 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));

對於訊息擴充欄位 message_bar,如果未設定該欄位,foo.GetExtension(message_bar) 會傳回一個未設定任何欄位的 Bar(可能是 Bar::default_instance())。

同樣地,對於重複的擴充欄位 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);

Arena 配置

Arena 配置是僅限 C++ 的功能,可協助您最佳化記憶體用量,並在使用 Protocol Buffers 時改善效能。在您的 .proto 中啟用 Arena 配置會為您的 C++ 產生的程式碼新增額外的程式碼,以用於處理 Arena。您可以在 Arena 配置指南 中找到有關 Arena 配置 API 的更多資訊。

服務

如果 .proto 檔案包含以下行

option cc_generic_services = true;

則 Protocol Buffer 編譯器將根據檔案中找到的服務定義產生程式碼,如本節所述。但是,產生的程式碼可能不是理想的程式碼,因為它未繫結到任何特定的 RPC 系統,因此比針對一個系統量身打造的程式碼需要更多的間接層級。如果您不希望產生此程式碼,請將此行新增至檔案

option cc_generic_services = false;

如果未給定以上任何一行,則選項預設為 false,因為一般服務已棄用。(請注意,在 2.4.0 之前,選項預設為 true

基於 .proto 語言服務定義的 RPC 系統應提供 外掛程式,以產生適用於系統的程式碼。這些外掛程式可能需要停用抽象服務,以便它們可以產生具有相同名稱的自己的類別。

本節的其餘部分說明在啟用抽象服務時,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 子類別化 Service 介面。Protocol Buffer 編譯器會自動產生 Service 方法的實作,如下所示

  • GetDescriptor:傳回服務的 ServiceDescriptor
  • CallMethod:根據提供的方法描述元判斷要呼叫的方法,並直接呼叫它,將請求和回應訊息物件向下轉換為正確的類型。
  • GetRequestPrototypeGetResponsePrototype:傳回給定方法之正確類型的請求或回應的預設執行個體。

也會產生以下靜態方法

  • static ServiceDescriptor descriptor():傳回類型的描述元,其中包含有關此服務具有哪些方法及其輸入和輸出類型的資訊。

Stub

Protocol Buffer 編譯器也會產生每個服務介面的「 Stub 」實作,用戶端希望將請求傳送至實作服務的伺服器會使用該實作。對於 Foo 服務(如上所述),將定義 Stub 實作 Foo_Stub。與巢狀訊息類型一樣,使用 typedef,以便 Foo_Stub 也可以稱為 Foo::Stub

Foo_StubFoo 的子類別,它也實作了以下方法

  • Foo_Stub(RpcChannel* channel):建構一個新的 Stub,該 Stub 在給定的通道上傳送請求。
  • Foo_Stub(RpcChannel* channel, ChannelOwnership ownership):建構一個新的 Stub,該 Stub 在給定的通道上傳送請求,並且可能擁有該通道。如果 ownershipService::STUB_OWNS_CHANNEL,則在刪除 Stub 物件時,它也會刪除通道。
  • RpcChannel* channel():傳回此 Stub 的通道,如同傳遞給建構函式一樣。

Stub 另外將每個服務的方法實作為通道周圍的包裝函式。呼叫其中一種方法只會呼叫 channel->CallMethod()

Protocol Buffer 程式庫不包含 RPC 實作。但是,它包含將產生的服務類別連接到您選擇的任何任意 RPC 實作所需的所有工具。您只需要提供 RpcChannelRpcController 的實作即可。如需更多資訊,請參閱 service.h 的文件。

外掛程式插入點

程式碼產生器外掛程式想要擴充 C++ 程式碼產生器的輸出,可以使用給定的插入點名稱插入以下類型的程式碼。每個插入點都會出現在 .pb.cc 檔案和 .pb.h 檔案中,除非另有說明。

  • includes:Include 指令。
  • namespace_scope:屬於檔案的套件/命名空間但不屬於任何特定類別的宣告。出現在所有其他命名空間範圍程式碼之後。
  • global_scope:屬於頂層、檔案命名空間外部的宣告。出現在檔案的最後。
  • class_scope:TYPENAME:屬於訊息類別的成員宣告。TYPENAME 是完整的 Proto 名稱,例如 package.MessageType。出現在類別中所有其他公用宣告之後。此插入點僅出現在 .pb.h 檔案中。

請勿產生依賴標準程式碼產生器宣告的私有類別成員的程式碼,因為這些實作詳細資料可能會在 Protocol Buffers 的未來版本中變更。