私が取り組んでいる製品では、非常に基本的なシナリオの1つがクラスのシリアル化です。通常、シリアル化されるクラスは、そのサブコンポーネントでシリアル化を呼び出します
たとえば、クラスstクラスA {B; C; D;}がある場合、A.PackはB、C、Dでpack関数を呼び出します。
そのようなクラスはたくさんあるので、同じパターンのコードを何度も複製する必要があります。この動作をパターンにカプセル化することは可能ですか(おそらくテンプレートと継承を使用して)
私が取り組んでいる製品では、非常に基本的なシナリオの1つがクラスのシリアル化です。通常、シリアル化されるクラスは、そのサブコンポーネントでシリアル化を呼び出します
たとえば、クラスstクラスA {B; C; D;}がある場合、A.PackはB、C、Dでpack関数を呼び出します。
そのようなクラスはたくさんあるので、同じパターンのコードを何度も複製する必要があります。この動作をパターンにカプセル化することは可能ですか(おそらくテンプレートと継承を使用して)
これを実現するのに役立つ可能性のある設計の1つは、複合パターンを使用することです。コンポーネント(ウィキペディアの図面から借用する)はPackableであり、次のようなことを実行できるテンプレートメソッドPack()を実装します。
GetChildren();
for each child:
child.Pack()
PackImpl();
PackImpl()はPackableの純粋仮想メソッドであり、継承するすべてのクラスが適切に実装します。GetChildren()は、反復のためにSTLコンテナ(おそらく空)を返します。子オブジェクトを格納するためのプライベートメンバーコレクションとともに、Packableに実装できます。基本的には、Packableからすべてのクラスを継承し、PackImpl()を実装すれば、完了です。
継承階層がメンバーである子ピースに直接依存している場合、これにより問題が発生することに注意してください。集約の観点から問題に取り組んだ場合、これはうまくいくはずです。
テンプレートにこれを行わせる通常の方法は、タイプリストを使用することです。
#include <iostream>
// typelist definition
struct Empty {};
template < typename H, typename T = Empty >
struct Cons {
typedef H head;
typedef T tail;
};
// interfaces all items support
class IPack
{
public:
virtual void Pack() = 0;
};
// some packable items
class Fee : public IPack
{
public:
virtual void Pack() {
std::cout << "Packed Fee\n";
}
};
class Fi : public IPack
{
public:
virtual void Pack() {
std::cout << "Packed Fi\n";
}
};
class Fo : public IPack
{
public:
virtual void Pack() {
std::cout << "Packed Fo\n";
}
};
class Fum : public IPack
{
public:
virtual void Pack() {
std::cout << "Packed Fum\n";
}
};
// these two templates create a composite IPack from a list
// of the types of its parts
template <typename Types>
class PackList : public PackList<typename Types::tail>
{
protected:
typedef typename Types::head Item;
Item item;
public:
virtual void Pack() {
item.Pack();
PackList<typename Types::tail>::Pack();
}
};
template <>
class PackList<Empty> : public IPack
{
public:
virtual void Pack() {}
};
// FeeFiFoFum is a composite of four items
class FeeFiFoFum : public PackList<Cons<Fee,Cons<Fi,Cons<Fo,Cons<Fum> > > > >
{
};
// create a FeeFiFoFum and call pack on it, which calls pack on its parts
int main ()
{
FeeFiFoFum giant;
giant.Pack();
}
タイプリストから作成されたコンポジットの適切な実装は、メンバーなどのアクセサーを提供しますが、これはそれらがどのように機能するかを示すのに十分であり、動作を指定せずにFee、Fi、Fo、およびFumをパックしたことを出力します。
ビジターパターンが役立つ可能性があります。
http://en.wikipedia.org/wiki/Visitor_pattern
これの考え方は、トラバーサルロジック(オブジェクトをステップスルーする)を各オブジェクトの処理から分離することです。この場合、オブジェクトごとのロジックは、単一のオブジェクトをシリアル化(エンコード)します(またはもちろん逆シリアル化します)。これはかなり単純で、通常のOOP手法を使用して最小限の繰り返しである必要があります。
トラバーサルとVisitorパターン固有のコードを実装するのは面倒ですが、ほとんどが定型文であり、1回限りのコードである必要があります。
あるコメント投稿者は次のように書いています。
「各メンバー変数のメソッドを自動的に呼び出すテンプレートを作成する方法はありますか?」という意味の場合、答えはノーです...
メソッドがデストラクタである場合、それに対する私の(少し邪悪な)カウンターはイエスです...
#include <iostream>
using namespace std;
bool Enable = false;
template <typename T>
class DS : public T {
public:
~DS() {
if (Enable) T::Serialize();
}
};
class A {
protected:
void Serialize() { cout << "A" << endl; }
};
class B {
protected:
void Serialize() { cout << "B" << endl; }
};
typedef DS<A> DSA;
typedef DS<B> DSB;
class C {
protected:
void Serialize() { cout << "C" << endl; }
private:
DSA a;
DSB b;
};
typedef DS<C> DSC;
int
main()
{
DSC c;
{
DSC c_copy = c;
Enable = true;
}
Enable = false;
}
出力は逆の順序であるため、オブジェクトを再構築するには、シリアル化されたデータを解析し、完成した各オブジェクトをスタックにプッシュする必要があります。複合オブジェクトは、スタックからポップオフする子の数を認識します。または、もちろん、シリアル化は中間構造に進むこともできます。
もう1つの興味深いアイデアは、起動時にこのハックを1回使用して(1つの特別なオブジェクトのみを作成して破棄する)、デストラクタからのコールバックが元のオブジェクトを記述するデータ構造を作成することです。
また、暗黙のコピーコンストラクターにも同様の悪用の可能性があり、順方向に実行される可能性があることにも注意してください...