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
ファイルの定義は簡単です。シリアライズしたい各データ構造に対して「メッセージ」を追加し、次にメッセージ内の各フィールドの名前と型を指定します。以下がメッセージを定義する .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
: フィールドは設定されていても、されていなくてもかまいません。optional なフィールド値が設定されていない場合、デフォルト値が使用されます。単純な型の場合、例の電話番号のtype
のように、独自のデフォルト値を指定できます。それ以外の場合、システムのデフォルトが使用されます。数値型の場合はゼロ、文字列の場合は空文字列、ブール値の場合は false です。埋め込みメッセージの場合、デフォルト値は常にメッセージの「デフォルトインスタンス」または「プロトタイプ」であり、そのフィールドは何も設定されていません。明示的に設定されていない optional (または required) フィールドの値を取得するためにアクセサを呼び出すと、常にそのフィールドのデフォルト値が返されます。repeated
: フィールドは何度でも(ゼロ回を含む)繰り返すことができます。繰り返された値の順序は protocol buffer 内で保持されます。repeated フィールドは動的サイズの配列と考えることができます。required
: フィールドには値を指定する必要があります。さもないと、メッセージは「初期化されていない」と見なされます。libprotobuf
がデバッグモードでコンパイルされている場合、初期化されていないメッセージをシリアライズするとアサーションエラーが発生します。最適化ビルドでは、チェックはスキップされ、メッセージはとにかく書き込まれます。ただし、初期化されていないメッセージのパースは常に失敗します(パースメソッドからfalse
が返されます)。これ以外では、required フィールドは optional フィールドとまったく同じように動作します。
重要
Required は永遠に フィールドをrequired
としてマークする際には非常に注意が必要です。ある時点で required フィールドの書き込みや送信を停止したい場合、そのフィールドを optional に変更するのは問題となります。古いリーダーは、このフィールドがないメッセージを不完全とみなし、意図せず拒否または破棄する可能性があります。代わりに、バッファに対してアプリケーション固有のカスタム検証ルーチンを作成することを検討してください。Google内では、required
フィールドは強く非推奨とされており、proto2構文で定義されたほとんどのメッセージは optional
と repeated
のみを使用しています。(Proto3は required
フィールドを一切サポートしていません。).proto
ファイルの書き方に関する完全なガイド(すべての可能なフィールドタイプを含む)は、Protocol Buffer 言語ガイド にあります。クラス継承に似た機能を探さないでください。protocol buffers はそれをサポートしていません。
Protocol Buffer のコンパイル
.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_
で始まります。また、各単数形(required または optional)フィールドには、そのフィールドが設定されている場合に true を返す has_
メソッドがあります。最後に、各フィールドには、フィールドを空の状態に戻す clear_
メソッドがあります。
数値の id
フィールドは上記で説明した基本的なアクセサセットしか持っていませんが、name
と email
フィールドは文字列であるため、いくつかの追加メソッドを持っています。文字列への直接ポインタを取得できる mutable_
ゲッターと、追加のセッターです。email
がまだ設定されていなくても mutable_email()
を呼び出すことができ、自動的に空の文字列に初期化されることに注意してください。この例で repeated message フィールドがあった場合、それも mutable_
メソッドを持ちますが、set_
メソッドは持ちません。
repeated フィールドにもいくつかの特別なメソッドがあります。repeated phones
フィールドのメソッドを見ると、以下のことができることがわかります。
- repeated フィールドの
_size
をチェックする(つまり、このPerson
に関連付けられている電話番号の数)。 - インデックスを使用して指定された電話番号を取得する。
- 指定されたインデックスの既存の電話番号を更新する。
- メッセージに別の電話番号を追加し、その後編集できるようにする(repeated スカラー型には、新しい値を渡すだけの
add_
があります)。
プロトコルコンパイラが特定のフィールド定義に対して生成するメンバーの正確な情報については、C++ 生成コードリファレンス を参照してください。
列挙型とネストされたクラス
生成されたコードには、.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;
: すべての required フィールドが設定されているか確認します。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 クラスをアプリケーション固有のクラスでラップすることです。.proto
ファイルの設計を制御できない場合(例えば、他のプロジェクトから再利用している場合)にも、protocol buffers をラップするのは良い考えです。その場合、ラッパークラスを使用して、アプリケーションのユニークな環境により適したインターフェースを作成できます。例えば、一部のデータやメソッドを隠したり、便利な関数を公開したりできます。生成されたクラスから継承して振る舞いを追加すべきではありません。これは内部メカニズムを破壊し、いずれにせよ良いオブジェクト指向の実践ではありません。メッセージの書き込み
それでは、protocol buffer クラスを使ってみましょう。まずアドレス帳アプリケーションにさせたいことは、個人の詳細をアドレス帳ファイルに書き込むことです。これを行うには、protocol buffer クラスのインスタンスを作成してデータを入力し、それを出力ストリームに書き込む必要があります。
これは、ファイルから AddressBook
を読み取り、ユーザー入力に基づいて新しい Person
を1つ追加し、新しい AddressBook
を再びファイルに書き戻すプログラムです。プロトコルコンパイラによって生成されたコードを直接呼び出すか参照する部分はハイライトされています。
#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 では、
- 既存のフィールドのフィールド番号を変更しては*いけません*。
- required フィールドを追加または削除しては*いけません*。
- optional または repeated フィールドを削除することは*できます*。
- 新しい optional または repeated フィールドを追加することは*できます*が、新しいフィールド番号(つまり、この protocol buffer で一度も使用されたことのないフィールド番号で、削除されたフィールドによって使用されたものでもない)を使用しなければなりません。
(これらのルールにはいくつかの例外がありますが、めったに使用されません。)
これらのルールに従えば、古いコードは新しいメッセージを問題なく読み取り、新しいフィールドは単に無視します。古いコードにとっては、削除された optional フィールドは単にデフォルト値を持ち、削除された repeated フィールドは空になります。新しいコードも古いメッセージを透過的に読み取ります。ただし、新しい optional フィールドは古いメッセージには存在しないため、has_
で設定されているかどうかを明示的にチェックするか、.proto
ファイルでフィールド番号の後に [default = value]
を付けて適切なデフォルト値を提供する必要があります。optional な要素にデフォルト値が指定されていない場合、型固有のデフォルト値が代わりに使用されます。文字列の場合、デフォルト値は空の文字列です。ブール値の場合、デフォルト値は false です。数値型の場合、デフォルト値はゼロです。また、新しい repeated フィールドを追加した場合、新しいコードはそれが(新しいコードによって)空のままにされたのか、それとも(古いコードによって)全く設定されなかったのかを区別できないことに注意してください。なぜなら、それには has_
フラグがないからです。
最適化のヒント
C++ Protocol Buffers ライブラリは非常に高度に最適化されています。しかし、適切な使用法によってパフォーマンスはさらに向上します。ライブラリから最後の一滴の速度を絞り出すためのヒントをいくつか紹介します。
- 可能な限りメッセージオブジェクトを再利用してください。メッセージは、クリアされたときでさえ、再利用のために割り当てたメモリを保持しようとします。したがって、同じ型で似たような構造のメッセージを連続して多数扱う場合、メモリ割り当ての負荷を軽減するために、毎回同じメッセージオブジェクトを再利用するのが良い方法です。しかし、オブジェクトは時間とともに肥大化することがあります。特に、メッセージの「形」が様々である場合や、時折通常よりもはるかに大きなメッセージを構築する場合です。
SpaceUsed
メソッドを呼び出してメッセージオブジェクトのサイズを監視し、大きくなりすぎたら削除すべきです。 - ご使用のシステムのメモリアロケータは、複数のスレッドから多数の小さなオブジェクトを割り当てるのに最適化されていない可能性があります。GoogleのTCMallocを試してみてください。
高度な使い方
Protocol buffers には、単純なアクセサやシリアライゼーションを超える用途があります。C++ API リファレンス を探求して、他に何ができるかを確認してください。
プロトコルメッセージクラスが提供する重要な機能の1つは、*リフレクション*です。特定のメッセージタイプに対してコードを書くことなく、メッセージのフィールドを反復処理し、その値を操作することができます。リフレクションの非常に便利な使い方の1つは、プロトコルメッセージをXMLやJSONなどの他のエンコーディングとの間で変換することです。リフレクションのより高度な使用法としては、同じタイプの2つのメッセージ間の違いを見つけたり、特定のメッセージ内容に一致する式を書くことができる一種の「プロトコルメッセージ用の正規表現」を開発したりすることが考えられます。想像力を働かせれば、Protocol Buffers を当初予想していたよりもはるかに広い範囲の問題に適用することが可能です!
リフレクションは Message::Reflection
インターフェース によって提供されます。