Protocol Bufferの基本: C++
このチュートリアルでは、C++プログラマーがProtocol Buffersを扱うための基本的な入門を提供します。簡単なサンプルアプリケーションの作成を通して、以下の方法を説明します。
.proto
ファイルでメッセージ形式を定義する。- Protocol Bufferコンパイラーを使用する。
- C++ Protocol Buffer APIを使用してメッセージを書き込み、読み取る。
これは、C++でProtocol Buffersを使用するための包括的なガイドではありません。より詳細なリファレンス情報については、Protocol Buffer言語ガイド (proto2)、Protocol Buffer言語ガイド (proto3)、C++ APIリファレンス、C++生成コードガイド、およびエンコーディングリファレンスを参照してください。
問題領域
ここで使用する例は、人々の連絡先の詳細をファイルとの間で読み書きできる、非常にシンプルな「アドレス帳」アプリケーションです。アドレス帳の各個人は、名前、ID、メールアドレス、および連絡先の電話番号を持っています。
このような構造化されたデータをシリアライズおよび取得するにはどうすればよいでしょうか?この問題を解決する方法はいくつかあります。
- 生のインメモリデータ構造は、バイナリ形式で送信/保存できます。長期的には、これは脆弱なアプローチです。受信/読み取りコードは、完全に同じメモリレイアウト、エンディアンなどでコンパイルする必要があるためです。また、ファイルが生の形式でデータを蓄積し、その形式に対応したソフトウェアのコピーが拡散すると、形式を拡張するのが非常に困難になります。
- データ項目を単一の文字列にエンコードするアドホックな方法を発明できます。たとえば、4つの整数を「12:3:-23:67」としてエンコードするなどです。これはシンプルで柔軟なアプローチですが、一度限りのエンコードおよびパースコードの記述が必要であり、パースにはわずかなランタイムコストがかかります。これは非常に単純なデータをエンコードするのに最適です。
- データをXMLにシリアライズします。XMLは(ある程度)人間が読める形式であり、多くの言語用のバインディングライブラリがあるため、このアプローチは非常に魅力的です。他のアプリケーション/プロジェクトとデータを共有したい場合には、良い選択肢となり得ます。しかし、XMLは非常にスペース効率が悪く、エンコード/デコードにはアプリケーションに大きなパフォーマンスペナルティを課す可能性があります。また、XML DOMツリーのナビゲーションは、通常クラス内の単純なフィールドをナビゲートするよりも、かなり複雑になります。
これらのオプションの代わりに、Protocol Buffersを使用できます。Protocol Buffersは、まさにこの問題を解決するための柔軟で効率的な自動化されたソリューションです。Protocol Buffersを使用すると、保存したいデータ構造の.proto
記述を記述します。それから、Protocol Bufferコンパイラーは、効率的なバイナリ形式でProtocol Bufferデータの自動エンコードとパースを実装するクラスを作成します。生成されたクラスは、Protocol Bufferを構成するフィールドのゲッターとセッターを提供し、Protocol Bufferをユニットとして読み書きする詳細を処理します。重要なことに、Protocol Buffer形式は、時間の経過とともに形式を拡張するという考え方をサポートしており、コードは古い形式でエンコードされたデータを引き続き読み取ることができます。
サンプルコードの場所
サンプルコードは、ソースコードパッケージの「examples」ディレクトリに含まれています。
プロトコル形式の定義
アドレス帳アプリケーションを作成するには、まず.proto
ファイルから始める必要があります。.proto
ファイル内の定義は簡単です。シリアライズするデータ構造ごとにmessageを追加し、メッセージ内の各フィールドの名前と型を指定します。以下は、メッセージを定義する.proto
ファイル、addressbook.proto
です。
syntax = "proto2";
package tutorial;
message Person {
optional string name = 1;
optional int32 id = 2;
optional string email = 3;
enum PhoneType {
PHONE_TYPE_UNSPECIFIED = 0;
PHONE_TYPE_MOBILE = 1;
PHONE_TYPE_HOME = 2;
PHONE_TYPE_WORK = 3;
}
message PhoneNumber {
optional string number = 1;
optional PhoneType type = 2 [default = PHONE_TYPE_HOME];
}
repeated PhoneNumber phones = 4;
}
message AddressBook {
repeated Person people = 1;
}
ご覧のとおり、構文はC++またはJavaに似ています。ファイルの各部分を見て、それが何をするのかを見ていきましょう。
.proto
ファイルは、パッケージ宣言で始まり、これは異なるプロジェクト間の名前の衝突を防ぐのに役立ちます。C++では、生成されたクラスはパッケージ名に一致する名前空間に配置されます。
次に、メッセージ定義があります。メッセージは、型付きフィールドのセットを含む集約体です。bool
、int32
、float
、double
、string
など、多くの標準的な単純データ型がフィールド型として利用できます。また、他のメッセージ型をフィールド型として使用することで、メッセージにさらに構造を追加することもできます。上記の例では、Person
メッセージにPhoneNumber
メッセージが含まれ、AddressBook
メッセージにPerson
メッセージが含まれています。他のメッセージ内にネストされたメッセージ型を定義することもできます。ご覧のとおり、PhoneNumber
型はPerson
内で定義されています。フィールドの1つに事前定義された値のリストのいずれかを持たせたい場合は、enum
型を定義することもできます。ここでは、電話番号が次の電話タイプのいずれかであることを指定します:PHONE_TYPE_MOBILE
、PHONE_TYPE_HOME
、またはPHONE_TYPE_WORK
。
各要素の「= 1」、「= 2」マーカーは、フィールドがバイナリエンコーディングで使用する一意のフィールド番号を識別します。フィールド番号1〜15は、より高い番号よりもエンコードに必要なバイト数が1バイト少ないため、最適化として、これらの番号を一般的に使用される要素または繰り返される要素に使用し、フィールド番号16以降をあまり使用されないオプション要素に残すことができます。繰り返されるフィールドの各要素はフィールド番号を再エンコードする必要があるため、繰り返されるフィールドはこの最適化に特に適しています。
各フィールドには、次の修飾子のいずれかを注釈する必要があります。
optional
: フィールドは設定されている場合とされていない場合があります。オプションのフィールド値が設定されていない場合、デフォルト値が使用されます。単純な型の場合、例の電話番号type
のように、独自のデフォルト値を指定できます。それ以外の場合は、システムデフォルトが使用されます。数値型の場合はゼロ、文字列の場合は空文字列、ブール値の場合はfalseです。埋め込みメッセージの場合、デフォルト値は常にメッセージの「デフォルトインスタンス」または「プロトタイプ」であり、フィールドは何も設定されていません。明示的に設定されていないオプション(または必須)フィールドの値を取得するためのアクセサを呼び出すと、常にそのフィールドのデフォルト値が返されます。repeated
: フィールドは任意の回数(ゼロを含む)繰り返される場合があります。繰り返される値の順序は、Protocol Bufferで保持されます。繰り返されるフィールドを動的サイズ配列と考えてください。required
: フィールドの値を提供する必要があります。そうしないと、メッセージは「初期化されていない」と見なされます。libprotobuf
がデバッグモードでコンパイルされている場合、初期化されていないメッセージをシリアライズすると、アサーションエラーが発生します。最適化されたビルドでは、チェックはスキップされ、メッセージはとにかく書き込まれます。ただし、初期化されていないメッセージをパースすると、常に失敗します(パースメソッドからfalse
が返されます)。これ以外は、必須フィールドはオプションフィールドとまったく同じように動作します。
重要
必須は永遠に フィールドをrequired
としてマークすることについては非常に注意する必要があります。ある時点で必須フィールドの書き込みまたは送信を停止したい場合、フィールドをオプションフィールドに変更するのは問題があります。古いリーダーは、このフィールドのないメッセージを不完全であると見なし、意図せずに拒否またはドロップする可能性があります。代わりに、バッファ用のアプリケーション固有のカスタム検証ルーチンを記述することを検討する必要があります。Google内では、required
フィールドは強く非推奨です。proto2構文で定義されたほとんどのメッセージは、optional
とrepeated
のみを使用します。(Proto3はrequired
フィールドをまったくサポートしていません。)可能なすべてのフィールド型を含む、.proto
ファイルの書き方の完全なガイドは、Protocol Buffer言語ガイドにあります。ただし、クラスの継承に似た機能を探さないでください。Protocol Bufferはそれを行いません。
Protocol Buffersのコンパイル
.proto
ができたので、次に必要なことは、AddressBook
(したがってPerson
とPhoneNumber
)メッセージを読み書きするために必要なクラスを生成することです。これを行うには、.proto
でProtocol Bufferコンパイラーprotoc
を実行する必要があります。
コンパイラーをインストールしていない場合は、パッケージをダウンロードし、READMEの手順に従ってください。
次に、コンパイラーを実行して、ソースディレクトリ(アプリケーションのソースコードが存在する場所。値を指定しない場合は現在のディレクトリが使用されます)、宛先ディレクトリ(生成されたコードの出力先。多くの場合
$SRC_DIR
と同じ)、および.proto
へのパスを指定します。この場合、あなたは…protoc -I=$SRC_DIR --cpp_out=$DST_DIR $SRC_DIR/addressbook.proto
C++クラスが必要なので、
--cpp_out
オプションを使用します。他のサポートされている言語にも同様のオプションが用意されています。
これにより、指定した宛先ディレクトリに次のファイルが生成されます。
addressbook.pb.h
、生成されたクラスを宣言するヘッダー。addressbook.pb.cc
、クラスの実装を含む。
Protocol Buffer API
生成されたコードのいくつかを見て、コンパイラーが作成したクラスと関数を見てみましょう。addressbook.pb.h
を見ると、addressbook.proto
で指定したメッセージごとにクラスがあることがわかります。Person
クラスを詳しく見ると、コンパイラーが各フィールドのアクセサを生成したことがわかります。たとえば、name
、id
、email
、およびphones
フィールドの場合、次のメソッドがあります。
// name
inline bool has_name() const;
inline void clear_name();
inline const ::std::string& name() const;
inline void set_name(const ::std::string& value);
inline void set_name(const char* value);
inline ::std::string* mutable_name();
// id
inline bool has_id() const;
inline void clear_id();
inline int32_t id() const;
inline void set_id(int32_t value);
// email
inline bool has_email() const;
inline void clear_email();
inline const ::std::string& email() const;
inline void set_email(const ::std::string& value);
inline void set_email(const char* value);
inline ::std::string* mutable_email();
// phones
inline int phones_size() const;
inline void clear_phones();
inline const ::google::protobuf::RepeatedPtrField< ::tutorial::Person_PhoneNumber >& phones() const;
inline ::google::protobuf::RepeatedPtrField< ::tutorial::Person_PhoneNumber >* mutable_phones();
inline const ::tutorial::Person_PhoneNumber& phones(int index) const;
inline ::tutorial::Person_PhoneNumber* mutable_phones(int index);
inline ::tutorial::Person_PhoneNumber* add_phones();
ご覧のとおり、ゲッターはフィールドと同じ名前を小文字で持ち、セッターメソッドはset_
で始まります。また、設定されているかどうかを示す各単数(必須またはオプション)フィールドにはhas_
メソッドがあります。最後に、各フィールドには、フィールドを空の状態に戻すclear_
メソッドがあります。
数値のid
フィールドには上記の基本的なアクセサセットがありますが、name
フィールドとemail
フィールドは文字列であるため、いくつかの追加メソッドがあります。文字列への直接ポインターを取得できるmutable_
ゲッターと、追加のセッターです。email
がまだ設定されていない場合でもmutable_email()
を呼び出すことができることに注意してください。空文字列に自動的に初期化されます。この例で繰り返されるメッセージフィールドがある場合、mutable_
メソッドも持ちますが、set_
メソッドは持ちません。
繰り返されるフィールドには、いくつかの特別なメソッドもあります。繰り返されるphones
フィールドのメソッドを見ると、次のことができることがわかります。
- 繰り返されるフィールドの
_size
(つまり、このPerson
に関連付けられている電話番号の数)を確認します。 - インデックスを使用して、指定された電話番号を取得します。
- 指定されたインデックスで既存の電話番号を更新します。
- 編集できる別の電話番号をメッセージに追加します(繰り返されるスカラー型には、新しい値を渡すことができる
add_
があります)。
特定のフィールド定義に対してProtocolコンパイラーが生成するメンバーの正確な詳細については、C++生成コードリファレンスを参照してください。
Enumとネストされたクラス
生成されたコードには、.proto
enumに対応するPhoneType
enumが含まれています。この型をPerson::PhoneType
として、その値をPerson::PHONE_TYPE_MOBILE
、Person::PHONE_TYPE_HOME
、およびPerson::PHONE_TYPE_WORK
として参照できます(実装の詳細はもう少し複雑ですが、enumを使用するために理解する必要はありません)。
コンパイラーは、Person::PhoneNumber
という名前のネストされたクラスも生成しました。コードを見ると、「実際の」クラスは実際にはPerson_PhoneNumber
と呼ばれていますが、Person
内で定義されたtypedefを使用すると、ネストされたクラスであるかのように扱うことができます。これが違いを生む唯一のケースは、別のファイルでクラスを前方宣言する場合です。C++ではネストされた型を前方宣言することはできませんが、Person_PhoneNumber
を前方宣言することはできます。
標準メッセージメソッド
各メッセージクラスには、メッセージ全体を確認または操作できる他の多くのメソッドも含まれています。これには、次のようなものがあります。
bool IsInitialized() const;
: すべての必須フィールドが設定されているかどうかを確認します。string DebugString() const;
: メッセージの人間が読める表現を返します。デバッグに特に役立ちます。void CopyFrom(const Person& from);
: 指定されたメッセージの値でメッセージを上書きします。void Clear();
: すべての要素を空の状態に戻します。
次のセクションで説明するこれらおよびI/Oメソッドは、すべてのC++ Protocol Bufferクラスで共有されるMessage
インターフェースを実装します。詳細については、Message
の完全なAPIドキュメントを参照してください。
パースとシリアライゼーション
最後に、各Protocol Bufferクラスには、Protocol Buffer バイナリ形式を使用して、選択した型のメッセージを書き込みおよび読み取るためのメソッドがあります。これらには以下が含まれます。
bool SerializeToString(string* output) const;
: メッセージをシリアライズし、指定された文字列にバイトを格納します。バイトはバイナリであり、テキストではないことに注意してください。ここでは便宜的なコンテナとしてstring
クラスのみを使用しています。bool ParseFromString(const string& data);
: 指定された文字列からメッセージをパースします。bool SerializeToOstream(ostream* output) const;
: メッセージを指定されたC++ostream
に書き込みます。bool ParseFromIstream(istream* input);
: メッセージを指定されたC++istream
からパースします。
これらは、パースとシリアライゼーションに用意されているオプションのほんの一部です。繰り返しますが、完全なリストについては、Message
APIリファレンスを参照してください。
重要
Protocol Buffersとオブジェクト指向設計 Protocol Bufferクラスは、基本的に追加機能を提供しないデータホルダー(Cの構造体のようなもの)であり、オブジェクトモデルの第一級市民には適していません。生成されたクラスにより豊富な動作を追加したい場合、これを行う最良の方法は、生成されたProtocol Bufferクラスをアプリケーション固有のクラスでラップすることです。Protocol Bufferをラップすることは、.proto
ファイルの設計を制御できない場合(たとえば、別のプロジェクトから再利用している場合など)にも良い考えです。その場合、ラッパークラスを使用して、アプリケーションの固有の環境により適したインターフェースを作成できます。一部のデータとメソッドを非表示にし、便利な関数を公開するなどです。生成されたクラスから継承して動作を追加しないでください。これは内部メカニズムを壊し、いずれにせよ優れたオブジェクト指向プラクティスではありません。メッセージの書き込み
Protocol Bufferクラスを使用してみましょう。アドレス帳アプリケーションで最初にできるようにしたいのは、個人の詳細をアドレス帳ファイルに書き込むことです。これを行うには、Protocol Bufferクラスのインスタンスを作成して入力し、それらを出力ストリームに書き込む必要があります。
これは、ファイルからAddressBook
を読み取り、ユーザー入力に基づいて新しいPerson
を1人追加し、新しいAddressBook
をファイルに再び書き出すプログラムです。Protocolコンパイラーによって生成されたコードを直接呼び出すか参照する部分は、強調表示されています。
#include <iostream>
#include <fstream>
#include <string>
#include "addressbook.pb.h"
using namespace std;
// This function fills in a Person message based on user input.
void PromptForAddress(tutorial::Person* person) {
cout << "Enter person ID number: ";
int id;
cin >> id;
person->set_id(id);
cin.ignore(256, '\n');
cout << "Enter name: ";
getline(cin, *person->mutable_name());
cout << "Enter email address (blank for none): ";
string email;
getline(cin, email);
if (!email.empty()) {
person->set_email(email);
}
while (true) {
cout << "Enter a phone number (or leave blank to finish): ";
string number;
getline(cin, number);
if (number.empty()) {
break;
}
tutorial::Person::PhoneNumber* phone_number = person->add_phones();
phone_number->set_number(number);
cout << "Is this a mobile, home, or work phone? ";
string type;
getline(cin, type);
if (type == "mobile") {
phone_number->set_type(tutorial::Person::PHONE_TYPE_MOBILE);
} else if (type == "home") {
phone_number->set_type(tutorial::Person::PHONE_TYPE_HOME);
} else if (type == "work") {
phone_number->set_type(tutorial::Person::PHONE_TYPE_WORK);
} else {
cout << "Unknown phone type. Using default." << endl;
}
}
}
// Main function: Reads the entire address book from a file,
// adds one person based on user input, then writes it back out to the same
// file.
int main(int argc, char* argv[]) {
// Verify that the version of the library that we linked against is
// compatible with the version of the headers we compiled against.
GOOGLE_PROTOBUF_VERIFY_VERSION;
if (argc != 2) {
cerr << "Usage: " << argv[0] << " ADDRESS_BOOK_FILE" << endl;
return -1;
}
tutorial::AddressBook address_book;
{
// Read the existing address book.
fstream input(argv[1], ios::in | ios::binary);
if (!input) {
cout << argv[1] << ": File not found. Creating a new file." << endl;
} else if (!address_book.ParseFromIstream(&input)) {
cerr << "Failed to parse address book." << endl;
return -1;
}
}
// Add an address.
PromptForAddress(address_book.add_people());
{
// Write the new address book back to disk.
fstream output(argv[1], ios::out | ios::trunc | ios::binary);
if (!address_book.SerializeToOstream(&output)) {
cerr << "Failed to write address book." << endl;
return -1;
}
}
// Optional: Delete all global objects allocated by libprotobuf.
google::protobuf::ShutdownProtobufLibrary();
return 0;
}
GOOGLE_PROTOBUF_VERIFY_VERSION
マクロに注意してください。C++ Protocol Bufferライブラリを使用する前にこのマクロを実行することは、厳密には必須ではありませんが、良い習慣です。これは、コンパイルに使用したヘッダーのバージョンと互換性のないバージョンのライブラリに誤ってリンクしていないことを確認します。バージョンの不一致が検出されると、プログラムは中止されます。すべての.pb.cc
ファイルは、起動時にこのマクロを自動的に呼び出すことに注意してください。
プログラムの最後にShutdownProtobufLibrary()
の呼び出しがあることにも注意してください。これが行うのは、Protocol Bufferライブラリによって割り当てられたグローバルオブジェクトを削除することだけです。プロセスはいずれにせよ終了し、OSがすべてのメモリの再利用を処理するため、これはほとんどのプログラムでは不要です。ただし、最後にオブジェクトを解放する必要があるメモリリークチェッカーを使用する場合、または単一のプロセスによって複数回ロードおよびアンロードされる可能性のあるライブラリを記述している場合は、Protocol Buffersにすべてをクリーンアップするように強制することができます。
メッセージの読み込み
もちろん、情報を取得できなければ、アドレス帳はあまり役に立ちません!この例では、上記の例で作成されたファイルを読み取り、その中のすべての情報を出力します。
#include <iostream>
#include <fstream>
#include <string>
#include "addressbook.pb.h"
using namespace std;
// Iterates though all people in the AddressBook and prints info about them.
void ListPeople(const tutorial::AddressBook& address_book) {
for (int i = 0; i < address_book.people_size(); i++) {
const tutorial::Person& person = address_book.people(i);
cout << "Person ID: " << person.id() << endl;
cout << " Name: " << person.name() << endl;
if (person.has_email()) {
cout << " E-mail address: " << person.email() << endl;
}
for (int j = 0; j < person.phones_size(); j++) {
const tutorial::Person::PhoneNumber& phone_number = person.phones(j);
switch (phone_number.type()) {
case tutorial::Person::PHONE_TYPE_MOBILE:
cout << " Mobile phone #: ";
break;
case tutorial::Person::PHONE_TYPE_HOME:
cout << " Home phone #: ";
break;
case tutorial::Person::PHONE_TYPE_WORK:
cout << " Work phone #: ";
break;
}
cout << phone_number.number() << endl;
}
}
}
// Main function: Reads the entire address book from a file and prints all
// the information inside.
int main(int argc, char* argv[]) {
// Verify that the version of the library that we linked against is
// compatible with the version of the headers we compiled against.
GOOGLE_PROTOBUF_VERIFY_VERSION;
if (argc != 2) {
cerr << "Usage: " << argv[0] << " ADDRESS_BOOK_FILE" << endl;
return -1;
}
tutorial::AddressBook address_book;
{
// Read the existing address book.
fstream input(argv[1], ios::in | ios::binary);
if (!address_book.ParseFromIstream(&input)) {
cerr << "Failed to parse address book." << endl;
return -1;
}
}
ListPeople(address_book);
// Optional: Delete all global objects allocated by libprotobuf.
google::protobuf::ShutdownProtobufLibrary();
return 0;
}
Protocol Bufferの拡張
Protocol Bufferを使用するコードをリリースした後、遅かれ早かれ、Protocol Bufferの定義を「改善」したくなるでしょう。新しいバッファに後方互換性を持たせ、古いバッファに前方互換性を持たせたい場合(そして、ほぼ確実にそうしたい場合)、従う必要のあるルールがいくつかあります。Protocol Bufferの新しいバージョンでは
- 既存のフィールドのフィールド番号を変更してはなりません。
- 必須フィールドを追加または削除してはなりません。
- オプションまたは繰り返されるフィールドを削除できます。
- 新しいオプションまたは繰り返されるフィールドを追加できますが、新しいフィールド番号を使用する必要があります(つまり、このProtocol Bufferでこれまで使用されたことのないフィールド番号であり、削除されたフィールドでも使用されていません)。
(これらのルールにはいくつかの例外がありますが、めったに使用されません。)
これらのルールに従うと、古いコードは新しいメッセージを問題なく読み取り、新しいフィールドを無視するだけです。古いコードにとって、削除されたオプションフィールドはデフォルト値を持ち、削除された繰り返されるフィールドは空になります。新しいコードも古いメッセージを透過的に読み取ります。ただし、新しいオプションフィールドは古いメッセージには存在しないことに注意してください。そのため、has_
で設定されているかどうかを明示的に確認するか、フィールド番号の後に[default = value]
を使用して.proto
ファイルで適切なデフォルト値を指定する必要があります。オプション要素にデフォルト値が指定されていない場合、代わりに型固有のデフォルト値が使用されます。文字列の場合、デフォルト値は空文字列です。ブール値の場合、デフォルト値はfalseです。数値型の場合、デフォルト値はゼロです。また、新しい繰り返されるフィールドを追加した場合、新しいコード(による)によって空のままにされたのか、古いコード(による)によってまったく設定されなかったのかを新しいコードで区別することはできません。繰り返されるフィールドにはhas_
フラグがないためです。
最適化のヒント
C++ Protocol Buffersライブラリは、非常に高度に最適化されています。ただし、適切な使用法により、パフォーマンスをさらに向上させることができます。ライブラリから最大限の速度を引き出すためのヒントをいくつか紹介します。
- 可能な場合はメッセージオブジェクトを再利用します。メッセージは、クリアされた場合でも、再利用のために割り当てたメモリを保持しようとします。したがって、同じ型で同様の構造を持つ多くのメッセージを連続して処理する場合は、毎回同じメッセージオブジェクトを再利用して、メモリアロケータの負荷を軽減することをお勧めします。ただし、オブジェクトは時間の経過とともに肥大化する可能性があります。特に、メッセージの「形状」が異なる場合や、通常よりもはるかに大きなメッセージを時々構築する場合はそうです。
SpaceUsed
メソッドを呼び出してメッセージオブジェクトのサイズを監視し、大きくなりすぎたら削除する必要があります。 - システムのメモリアロケータは、複数のスレッドから多数の小さなオブジェクトを割り当てるのに最適化されていない可能性があります。GoogleのTCMallocを代わりに使用してみてください。
高度な使用法
Protocol Buffersには、単純なアクセサとシリアライゼーションを超える用途があります。C++ APIリファレンスを調べて、他に何ができるかを確認してください。
Protocolメッセージクラスによって提供される重要な機能の1つは、リフレクションです。特定のメッセージ型に対してコードを記述せずに、メッセージのフィールドを反復処理し、それらの値を操作できます。リフレクションを使用する非常に便利な方法の1つは、ProtocolメッセージをXMLやJSONなどの他のエンコーディングとの間で変換することです。リフレクションのより高度な使用法は、同じ型の2つのメッセージ間の違いを見つけること、または特定メッセージの内容に一致する式を記述できる「Protocolメッセージの正規表現」のようなものを開発することかもしれません。想像力を働かせれば、Protocol Buffersを最初に予想されるよりもはるかに幅広い問題に適用することが可能です!
リフレクションは、Message::Reflection
インターフェースによって提供されます。