アリスは物理転送機を持っている

まだまだ続くアリプラシリーズ。目次は後で作成します。既に自分で何を書いているのか忘れているし。
ひとまず、物理転送機を作ったところで、かつて流行ったオブジェクトパターンの再発明なのかなぁと思ったりします。このシリーズ、特にパターンを使っている訳ではなくて、自分がいままで業務で使ったパターンを書き残しているので、

  1. アリス&プラダという組み合わせで、C++ で現実をモデル化してみる。
  2. C++ でのコードを書いてみる。
  3. そういえば、オブジェクトパターンでこんなのがあったよなぁ。

という流れになっています。

さて、物理転送機、というのは別のところにコピーを作れるっていう方法です。今回は、ひとつの棚にプラダを入れて、そして取り出して、もう一度棚に入れる、という動作になります。取り出すっていうのは、いわゆる永続化なんですが、C++ の場合は単なるバイナリデータとして扱えますよって話です。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
// for アリスは物理転送機を持っている
 
#include <string.h>
#include <iostream>
#include <string>
using namespace std;
 
class ISerialize {
    // シリアライズ化
    void *Serialize();
    // データから復元
    static void Serialize( void * );
};
 
class Bag : public ISerialize
{
public:
    // 利用フラグ
    bool used ;
    // 持ち主の名前
    char ownerName[30];
    // ブランド名
    char brandName[30];
public:
    // コンストラクタ
    Bag() : used(false) {
        ownerName[0] = '\0';
        brandName[0] = '\0';
    }
    // アクセッサ
    bool getUsed() { return used; }
    string getOwnerName() { return ownerName; }
    void setOwnerName( string name ) {
        if ( ownerName[0] == '\0' ) {
            strcpy( ownerName, (const char*)name.c_str() );
        }
         
    }
    string getBrandName() { return brandName; }
    void setBrandName( string name ) {
        if ( brandName[0] == '\0' ) {
            strcpy( brandName, (const char*)name.c_str() );
        }
    }
    // 利用する
    virtual void Use() {
        this->used = true;
    }
    // シリアライズ
    virtual void *Serialize() {
        int size = sizeof(Bag);
        void *data = new char[size];
        memcpy( data,this, size );
        return data;
    }
    // 逆シリアライズ
    static Bag *Serialize(void *data) {
        Bag *bag = (Bag*)data;
        return bag;
    }
};
class Prada : public Bag
{
public:
    Prada() : Bag() {
        strcpy( brandName, "Prada" );
    }
};
class Tiffany : public Bag
{
public:
    Tiffany() : Bag() {
        strcpy( brandName, "Tiffany" );
    }
};
 
 
class Alice
{
private:
    Bag *bag;
public:
    Alice() : bag(NULL) {
    }
    void Present( Bag *b )
    {
        if ( this->bag == NULL ) {
            this->bag = b;
            this->bag->setOwnerName("Alice");
        }
    }
    void *SaveMyBag() {
        void *data = bag->Serialize();
        bag = NULL;
        return data;
    }
    void LoadMyBag(void *data) {
        if ( this->bag == NULL ) {
            Bag *bag = Bag::Serialize( data );
            this->bag = bag;
        }
    }
    string GetBrandName() {
        if ( bag == NULL ) {
            return "none";
        } else {
            return bag->getBrandName();
        }
    }
};
 
 
int main(void)
{
    Alice alice;
 
    Bag *bag = new Prada();
    // アリスにプラダをプレゼント
    alice.Present( bag );
    cout << "alice's bag brand is " << alice.GetBrandName() << endl;
    // 一度、棚から降ろして持っていない状態にする
    void *prada = alice.SaveMyBag();
    cout << "alice's bag brand is " << alice.GetBrandName() << endl;
    // アリスにティファニーをプレゼント
    bag = new Tiffany();
    alice.Present( bag );
    cout << "alice's bag brand is " << alice.GetBrandName() << endl;
    // ティファニーを棚卸して
    void *tiffany = alice.SaveMyBag();
    // 再び、プラダを棚に入れる
    alice.LoadMyBag( prada );
    cout << "alice's bag brand is " << alice.GetBrandName() << endl;
 
    return 0;
}

ひとまず実行すると、こんな感じ。

1
2
3
4
5
D:\work\blog\src\alice>a
alice's bag brand is Prada
alice's bag brand is none
alice's bag brand is Tiffany
alice's bag brand is Prada

ここでは、永続化ということでシリアライズがミソです。インターフェースにしてあるのは、気分の問題でして、シリアライズのコードはこんなに簡単。

1
2
3
4
5
6
7
8
9
10
11
12
// シリアライズ
virtual void *Serialize() {
    int size = sizeof(Bag);
    void *data = new char[size];
    memcpy( data,this, size );
    return data;
}
// 逆シリアライズ
static Bag *Serialize(void *data) {
    Bag *bag = (Bag*)data;
    return bag;
}

クラスのバイトデータを保持しているだけなんですが、実はこの技が使えるのは C/C++ だけなんですね。Java, C#, VB なんてのは使えません。シリアライズする時に何らかのフォーマッティング(大抵は XML なんですが)を使わない限り無理です。

この手のシリアライズ、C++ が得意とするとこで、データの読み書きがバイナリで直接行われるために非常に高速に動きます。っていうか、もともと C言語の struct 互換の動きになっているので、こういう風に動くわけでして、フォーマットを気にせずに単純に sizeof(…) でバイトデータを書き込めるのが得意なのですね。

# 勿論、このバイナリコードが人にとっては謎な値なので、XML フォーマットというのが流行りなわけですが。後、構造体/クラスをバイナリのままやり取りする方法は、固定長のデータしか無理なんですよね。なので、文字列のところで string じゃなくて char name[30] となっているのはそれが理由です。

データ構造を気にしなくて、高速に読み書きができるので、利用どころとしてはデータのヘッダ部分なんかに良く使われます。そう BMP 形式のヘッダとか、struct なんとかで読みだした後に、データ長を算出するときなんか、struct で読み込めば一発です、という話ですね。

カテゴリー: C++ パーマリンク