C++ 生成程式碼指南

精確描述了協議緩衝區編譯器為任何給定的協議定義生成的 C++ 程式碼。

proto2、proto3 和 editions 生成程式碼之間的任何差異都會在此處突出顯示。請注意,這些差異是本文件所述的生成程式碼中的差異,而不是基礎訊息類/介面中的差異,後者在所有版本中都是相同的。在閱讀本文件之前,您應該閱讀 proto2 語言指南proto3 語言指南2023 年版語言指南

編譯器呼叫

Protocol buffer 編譯器在呼叫 --cpp_out= 命令列標誌時會生成 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;它們必須已經存在。

包(Packages)

如果 .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.solibprotobuf.lib)。“lite”庫比完整庫小得多,更適合資源受限的系統,例如手機。

應該建立自己的 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*

注意: 複製建構函式和賦值運算子會深度複製訊息資料。這確保了每個訊息物件都擁有並管理自己的資料副本,從而防止了諸如雙重釋放或釋放後使用之類的錯誤。此行為與標準 C++ 中擁有其資料的物件(如 std::vector)的實踐一致。對於來自具有不同複製語義的語言(例如 JavaScript 或 TypeScript,其中淺複製可能更常見)的開發者來說,重要的是要注意對複製的訊息的修改不會影響原始訊息,反之亦然。

該類還定義了以下靜態方法:

  • static const Descriptor* descriptor():返回型別的描述符。它包含有關型別的資訊,包括它有哪些欄位以及它們的型別是什麼。這可以與 反射 一起用於以程式設計方式檢查欄位。
  • static const Foo& default_instance():返回 Foo 的常量單例例項,該例項與新構造的 Foo 例項相同(因此所有單一欄位都未設定,所有重複欄位都為空)。請注意,訊息的預設例項可以透過呼叫其 New() 方法作為工廠來使用。

Abseil flag 支援

Message 物件對 Abseil 的標誌解析/取消解析邏輯具有原生支援。它們可用作 ABSL_FLAG 宣告的型別。標誌語法為 :format,options...:value,其中

  • formattextserialized 之一。
  • options 是一個可能為空的選項列表。每種格式都有其支援的選項。
  • value 是指定格式的有效負載。

有效的選項是:

  • 對於 text
    • base64:表示 value 已編碼為 base64。
    • ignore_unknown:指定時,未知欄位/副檔名將被丟棄。否則,它們會導致解析失敗。
  • 對於 serialized
    • base64:表示 value 已編碼為 base64。建議將 serializedbase64 一起使用,因為在 shell 中傳遞二進位制資料既困難又容易出錯。

請注意,訊息型別的標誌支援不適用於 LITE_RUNTIME 配置。

示例

ABSL_FLAG(MyProtoType, my_proto_config, {},
          "This is a proto config description.");

巢狀型別

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

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 繼承的任何方法或透過其他方式訪問訊息(例如,透過複製訊息使用複製建構函式)。相應地,返回指標的值從不保證在訪問器的兩次不同調用之間保持相同。

生成的欄位名

保留的關鍵字會在生成的輸出中附加下劃線。

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

顯式存在數值欄位

對於具有 顯式存在性 的數值欄位的欄位定義

int32 foo = 1;

編譯器將生成以下訪問器方法

  • bool has_foo() const:如果欄位已設定,則返回 true
  • int32_t foo() const:返回欄位的當前值。如果欄位未設定,則返回預設值。
  • void set_foo(::int32_t value):設定欄位的值。呼叫此方法後,has_foo() 將返回 truefoo() 將返回 value
  • void clear_foo():清除欄位的值。呼叫此方法後,has_foo() 將返回 falsefoo() 將返回預設值。

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

隱式存在數值欄位

對於具有 隱式存在性 的數值欄位的欄位定義

int32 foo = 1;

編譯器將生成以下訪問器方法

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

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

顯式存在字串/位元組欄位

注意: 自 2023 年版起,如果 features.(pb.cpp).string_type 設定為 VIEW,則會生成 string_view API。

對於具有 顯式存在性 的這些欄位定義

string foo = 1;
bytes foo = 2;

編譯器將生成以下訪問器方法

  • bool has_foo() const:如果欄位已設定,則返回 true

  • const string& foo() const:返回欄位的當前值。如果欄位未設定,則返回預設值。

  • void set_foo(...):設定欄位的值。呼叫此方法後,has_foo() 將返回 truefoo() 將返回 value 的副本。

  • string* mutable_foo():返回一個指向儲存欄位值的可修改 string 物件的指標。如果呼叫前欄位未設定,則返回的字串將為空(不是預設值)。呼叫此方法後,has_foo() 將返回 truefoo() 將返回寫入給定字串的任何值。

    注意: 此方法將在新的 string_view API 中刪除。

  • void clear_foo():清除欄位的值。呼叫此方法後,has_foo() 將返回 falsefoo() 將返回預設值。

  • void set_allocated_foo(string* value):將 string 物件設定到欄位並釋放先前存在的欄位值(如果存在)。如果 string 指標不是 NULL,則訊息將擁有分配的 string 物件,並且 has_foo() 將返回 true。訊息可以隨時刪除分配的 string 物件,因此指向該物件的引用可能會失效。否則,如果 valueNULL,則行為與呼叫 clear_foo() 相同。

  • string* release_foo():釋放欄位的所有權並返回 string 物件的指標。呼叫此方法後,呼叫者將擁有分配的 string 物件,has_foo() 將返回 falsefoo() 將返回預設值。

隱式存在字串/位元組欄位

注意: 自 2023 年版起,如果 features.(pb.cpp).string_type 設定為 VIEW,則會生成 string_view API。

對於具有 隱式存在性 的這些欄位定義

string foo = 1 [features.field_presence = IMPLICIT];
bytes foo = 1 [features.field_presence = IMPLICIT];

編譯器將生成以下訪問器方法

  • const string& foo() const:返回欄位的當前值。如果欄位未設定,則返回空字串/空位元組。
  • void set_foo(Arg_&& arg, Args_... args):設定欄位的值。呼叫此方法後,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 儲存資料,請使用以下語法:

// edition (default settings)
bytes foo = 25 [ctype=CORD];
bytes foo = 26 [ctype=CORD, features.field_presence = IMPLICIT];

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

編譯器將生成以下訪問器方法

  • const ::absl::Cord& foo() const:返回欄位的當前值。如果欄位未設定,則返回空 Cord(proto3)或預設值(proto2 和 editions)。
  • void set_foo(const ::absl::Cord& value):設定欄位的值。呼叫此方法後,foo() 將返回 value
  • void set_foo(::absl::string_view value):設定欄位的值。呼叫此方法後,foo() 將返回 value 作為 absl::Cord
  • void clear_foo():清除欄位的值。呼叫此方法後,foo() 將返回空 Cord(proto3)或預設值(proto2 和 editions)。
  • bool has_foo():如果欄位已設定,則返回 true。僅適用於 proto3 中的 optional 欄位和 editions 中的顯式存在性欄位。

顯式存在列舉欄位

給定列舉型別

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

對於具有 顯式存在性 的此欄位定義

Bar bar = 1;

編譯器將生成以下訪問器方法

  • bool has_bar() const:如果欄位已設定,則返回 true
  • Bar bar() const:返回欄位的當前值。如果欄位未設定,則返回預設值。
  • void set_bar(Bar value):設定欄位的值。呼叫此方法後,has_bar() 將返回 truebar() 將返回 value。在除錯模式下(即,未定義 NDEBUG),如果 valueBar 的任何已定義值都不匹配,此方法將中止程序。
  • void clear_bar():清除欄位的值。呼叫此方法後,has_bar() 將返回 falsebar() 將返回預設值。

隱式存在列舉欄位

給定列舉型別

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

對於具有 隱式存在性 的此欄位定義

Bar bar = 1;

編譯器將生成以下訪問器方法

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

顯式存在嵌入訊息欄位

給定訊息型別:

message Bar {}

對於具有 顯式存在性 的此欄位定義

Bar bar = 1;

編譯器將生成以下訪問器方法

  • bool has_bar() const:如果欄位已設定,則返回 true
  • const Bar& bar() const:返回欄位的當前值。如果欄位未設定,則返回一個欄位未設定的 Bar(可能是 Bar::default_instance())。
  • Bar* mutable_bar():返回一個指向儲存欄位值的可修改 Bar 物件的指標。如果呼叫前欄位未設定,則返回的 Bar 將沒有欄位被設定(即,它將與新分配的 Bar 相同)。呼叫此方法後,has_bar() 將返回 truebar() 將返回對同一個 Bar 例項的引用。
  • void clear_bar():清除欄位的值。呼叫此方法後,has_bar() 將返回 falsebar() 將返回預設值。
  • void set_allocated_bar(Bar* value):將 Bar 物件設定到欄位並釋放先前存在的欄位值(如果存在)。如果 Bar 指標不是 NULL,則訊息將擁有分配的 Bar 物件,並且 has_bar() 將返回 true。否則,如果 BarNULL,則行為與呼叫 clear_bar() 相同。
  • Bar* release_bar():釋放欄位的所有權並返回 Bar 物件的指標。呼叫此方法後,呼叫者將擁有分配的 Bar 物件,has_bar() 將返回 falsebar() 將返回預設值。

重複數值欄位

對於此欄位定義:

repeated int32 foo = 1;

編譯器將生成以下訪問器方法

  • int foo_size() const:返回欄位中當前元素的數量。要檢查空集合,請考慮使用底層 RepeatedField 中的 empty() 方法而不是此方法。
  • int32_t foo(int index) const:返回給定零基索引處的元素。使用超出 [0, foo_size()) 範圍的索引呼叫此方法會導致未定義行為。
  • void set_foo(int index, int32_t value):設定給定零基索引處元素的 قيمة。
  • void add_foo(int32_t value):將具有給定值的元素追加到欄位末尾。
  • void clear_foo():從欄位中刪除所有元素。呼叫此方法後,foo_size() 將返回零。
  • const RepeatedField<int32_t>& foo() const:返回儲存欄位元素的底層 RepeatedField。此容器類提供類似 STL 的迭代器和其他方法。
  • RepeatedField<int32_t>* mutable_foo():返回儲存欄位元素的底層可修改 RepeatedField 的指標。此容器類提供類似 STL 的迭代器和其他方法。

對於其他數值欄位型別(包括 bool),int32_t 會根據 標量值型別表 替換為相應的 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),如果 valueBar 的任何已定義值都不匹配且是封閉列舉,此方法將中止程序。
  • void add_bar(Bar value):將具有給定值的元素追加到欄位末尾。在除錯模式下(即,未定義 NDEBUG),如果 valueBar 的任何已定義值都不匹配,此方法將中止程序。
  • 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 情況是 kFoo,則返回 true
  • int32 foo() const:如果 oneof 情況是 kFoo,則返回欄位的當前值。否則,返回預設值。
  • void set_foo(int32 value):
    • 如果同一 oneof 中的任何其他 oneof 欄位已設定,則呼叫 clear_example_name()
    • 設定此欄位的值並將 oneof 情況設定為 kFoo
    • has_foo() 將返回 true,foo() 將返回 valueexample_name_case() 將返回 kFoo
  • void clear_foo():
    • 如果 oneof 情況不是 kFoo,則不會進行任何更改。
    • 如果 oneof 情況是 kFoo,則清除欄位值和 oneof 情況。has_foo() 將返回 falsefoo() 將返回預設值,example_name_case() 將返回 EXAMPLE_NAME_NOT_SET

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

Oneof 字串欄位

注意: 自 2023 年版起,可能會生成 string_view API。

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

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

編譯器將生成以下訪問器方法

  • bool has_foo() const:如果 oneof 情況是 kFoo,則返回 true
  • const string& foo() const:如果 oneof 情況是 kFoo,則返回欄位的當前值。否則,返回預設值。
  • void set_foo(::absl::string_view value):
    • 如果同一 oneof 中的任何其他 oneof 欄位已設定,則呼叫 clear_example_name()
    • 設定此欄位的值並將 oneof 情況設定為 kFoo
    • has_foo() 將返回 truefoo() 將返回 value 的副本,example_name_case() 將返回 kFoo
  • void set_foo(const string& value):與第一個 set_foo() 類似,但從 const 字串引用複製。
  • void set_foo(string&& value):與第一個 set_foo() 類似,但移動傳遞的字串。
  • 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() 將返回 trueexample_name_case() 將返回 kFoo
    • 如果字串指標為 NULL,則 has_foo() 將返回 falseexample_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,則返回欄位的當前值。否則,返回一個欄位未設定的 Bar(可能是 Bar::default_instance())。
  • void set_bar(Bar value):
    • 如果同一 oneof 中的任何其他 oneof 欄位已設定,則呼叫 clear_example_name()
    • 設定此欄位的值並將 oneof 情況設定為 kBar
    • has_bar() 將返回 truebar() 將返回 valueexample_name_case() 將返回 kBar
    • 在除錯模式下(即,未定義 NDEBUG),如果 valueBar 的任何已定義值都不匹配且是封閉列舉,此方法將中止程序。
  • 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
    • 如果指標為 NULL,則 has_bar() 將返回 falseexample_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<int32, int32> weight = 1;

編譯器將生成以下訪問器方法

  • const google::protobuf::Map<int32, int32>& weight();:返回一個不可修改的 Map
  • google::protobuf::Map<int32, int32>* mutable_weight();:返回一個可修改的 Map

google::protobuf::Map 是一種特殊的容器型別,在協議緩衝區中用於儲存 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 和 editions 中的處理方式不同。在 proto2 中,整個 map 條目訊息將被放入包含訊息的未知欄位集中。在 proto3 中,它被放入 map 欄位,就好像它是已知的列舉值一樣。使用 editions 時,預設情況下它會映象 proto3 的行為。如果 features.enum_type 設定為 CLOSED,則它會映象 proto2 的行為。

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 欄位集使用指標(Message 或 String),則釋放物件,並將 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 中已更改。將任何整數安全地轉換為 proto3 列舉值,只要它適合 int32。在解析 proto3 訊息時,無效的列舉值也會被保留,並透過列舉欄位訪問器返回。

在使用 proto3 和 editions 列舉進行 switch 語句時要小心。 Proto3 和 editions 列舉是開放列舉型別,可能存在超出指定符號範圍的值。(Editions 列舉可以使用 enum_type 功能設定為封閉列舉。)對於開放列舉型別,未識別的列舉值在解析訊息時將被保留,並透過列舉欄位訪問器返回。即使列出了所有已知欄位,在沒有預設 case 的開放列舉上的 switch 語句也無法捕獲所有情況。這可能導致意外行為,包括資料損壞和執行時崩潰。始終新增 default case 或在 switch 外部顯式呼叫 Foo_IsValid(int) 來處理未知列舉值。

您可以在訊息型別內部定義一個列舉。在這種情況下,protocol buffer 編譯器生成的程式碼會使其看起來好像列舉型別本身被宣告為巢狀在訊息的類中。Foo_descriptor()Foo_IsValid() 函式被宣告為靜態方法。實際上,列舉型別本身及其值在全域性範圍內以 mangled 名稱宣告,並透過 typedef 和一系列常量定義匯入到類的作用域中。這是為了解決宣告順序問題。不要依賴 mangled 的頂層名稱;假裝列舉確實巢狀在訊息類中。

Abseil flag 支援

生成的 enum 值對 Abseil 的標誌解析/取消解析邏輯具有原生支援。它們可用作 ABSL_FLAG 宣告的型別。

標誌解析器同時支援標籤和數字。無效的標籤/數字會導致解析失敗。

擴充套件 (僅限 proto2 和 editions)

給定一個帶有擴充套件範圍的訊息

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_extBaz 內部宣告。它可以如下使用:

Foo foo;
Baz* baz = foo.MutableExtension(Baz::foo_ext);
FillInMyBaz(baz);

Arena 分配

Arena 分配是一項僅 C++ 的功能,可幫助您最佳化記憶體使用並提高處理協議緩衝區時的效能。在 .proto 檔案中啟用 Arena 分配會為使用 Arena 新增額外的程式碼到您的 C++ 生成程式碼中。您可以在 Arena 分配指南 中找到有關 Arena 分配 API 的更多資訊。

服務(Services)

如果 .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 回撥。實現自己的服務時,必須繼承此生成的服務並根據需要實現其方法。

FooService 介面的子類。Protocol Buffer 編譯器會自動生成 Service 方法的實現,如下所示:

  • GetDescriptor:返回服務的 ServiceDescriptor
  • CallMethod:根據提供的方法描述符確定正在呼叫哪個方法,並直接呼叫它,將請求和響應訊息物件向下轉換為正確的型別。
  • GetRequestPrototypeGetResponsePrototype:為給定方法返回正確型別的請求或響應的預設例項。

還生成了以下靜態方法:

  • static ServiceDescriptor descriptor():返回型別的描述符,其中包含有關此服務有哪些方法以及它們的輸入和輸出型別的資訊。

存根

Protocol buffer 編譯器還為每個服務介面生成一個“stub”實現,供希望向實現該服務的伺服器傳送請求的客戶端使用。對於(前面描述的)Foo 服務,將定義 stub 實現 Foo_Stub。與巢狀訊息型別一樣,使用 typedef,因此 Foo_Stub 也可以稱為 Foo::Stub

Foo_StubFoo 的子類,它還實現了以下方法:

  • 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 的實現。有關更多資訊,請參閱 service.h 的文件。

外掛插入點

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

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

不要生成依賴於標準程式碼生成器宣告的私有類成員的程式碼,因為這些實現細節在未來版本的 Protocol Buffers 中可能會改變。