オブジェクトをバイトとの間でシリアル化するには、最終的に、サポートする必要のあるタイプごとに2つの関数(プリミティブ、オブジェクトなど)が必要です。これらは「Load()」と「Store()」です。
理想的には、バイトに固定インターフェース(iostream、char *、一部のバッファーオブジェクトなど)を使用します。論理的にはそれがその役割であるため、読みやすくするために「ByteBuffer」と呼びましょう。
これで、Serializableコンセプトのテンプレート関数のようなものができました。
template<typename T>
ByteBuffer Store(const T& object) { // BUT, What goes here...? }
template<typename T>
T Load(const ByteBuffer& bytes);
さて、これはプリミティブ型以外では機能しません。たとえこれらの「訪問者」または何かを作成したとしても、彼らは文字通り、オブジェクトの内部に関するすべての詳細を知って仕事をする必要があります。さらに、「Load()」は論理的にコンストラクターです(実際には、簡単に失敗する可能性があるため、FACTORYです)。これらを実際のオブジェクトに関連付ける必要があります。
Serializableを基本クラスにするには、「不思議なことに繰り返されるテンプレート」パターンを使用する必要があります。これを行うには、すべての派生クラスに次の形式のコンストラクターが必要です。
T(const ByteBuffer& bytes);
エラーをチェックするために、派生コンストラクターが設定できる基本クラスに保護フラグ「valid」を提供できます。Load()が適切に機能するには、オブジェクトがファクトリスタイルの構築をサポートしている必要があることに注意してください。
これで、これを正しく実行でき、ファクトリとして「ロード」を提供できます。
template<typename T>
class Serializable // If you do reference-counting on files & such, you can add it here
{
protected:
bool valid;
// Require derived to mark as valid upon load
Serializable() : valid(false) {}
virtual ~Serializable() { valid = false; }
public:
static T Load(const ByteBuffer& bytes); // calls a "T(bytes)" constructor
// Store API
virtual ByteBuffer Store() = 0; // Interface details are up to you.
};
さて、そのように基本クラスから派生するだけで、必要なものすべてを手に入れることができます。
class MyObject : public Serializable<MyObject>
{
protected:
// .. some members ...
MyObject(const ByteBuffer& bytes)
{
//... Actual load logic for this object type ...
// On success only:
valid = true;
}
public:
virtual ByteBuffer Store() {
//... store logic
}
};
すばらしいのは、「MyObject :: Load()」を呼び出すことができ、期待どおりに動作することです。さらに、「ロード」をオブジェクトを構築する唯一の方法にすることができ、読み取り専用ファイルなどのAPIをクリーンアップできます。
これを完全なファイルAPIに拡張するには、もう少し作業が必要です。つまり、より大きなバッファーから読み取ることができる「Load()」(他のものを保持する)と、既存のバッファーに追加する「Store()」を追加します。
ちなみに、これにはBoostのAPIを使用しないでください。優れた設計では、シリアル化可能なオブジェクトは、ディスク上のプリミティブ型のパックされた構造に1対1でマップする必要があります。これが、結果のファイルを他のプログラムまたは他のマシンで実際に使用できる唯一の方法です。Boostは、後で後悔することをほとんど実行できる恐ろしいAPIを提供します。