セーブ・ロードの自動化 その3(メモリブロックの扱い)
ここまで通常のローカルなメンバ変数、CSaveObjBaseクラスから派生されたオブジェクトのポインタによるオブジェクト間の繋がりを自動的にセーブロードする機構を作ってきました。この節ではさらに発展させて、メモリブロックをセーブロードする機構を考えて見ましょう。
@ メモリブロックデータタイプTYPE_MEMの新設
文字列のような可変長のメモリブロックは、ポインタだけを見てそのサイズを知ることはできません(文字列はナル文字(\0)でそれを判断しているだけです)。そこで、メモリブロックのサイズを格納する変数を設け、それをセーブロードに利用します。まず、メモリブロックを表すポインタメンバ変数に対してDATARECORD::typeにTYPE_MEMデータタイプを新設します。
保存したいメモリブロックへのポインタをm_pMemBlock、そのサイズをm_iMemSizeと宣言しているとしましょう。これらの変数を使って次のように配列を宣言します。
// 保存する変数の位置を指定
DATARECORD CPlayer::m_gDataRecord[] =
{
{TYPE_MEM, (int)&((CPlayer*)0)->m_pMemBlock, (int)&((CPlayer*)0)->m_iMemSize }
};
第1番目にはデータタイプTYPE_MEMを指定します。第2引数はポインタ変数へのオフセット値です。ここまではTYPE_LOCALと一緒です。特徴は3つ目です。ここにメモリブロックのサイズを示す変数へのオフセット値を指定します。つまりDATARECORD構造体の中に2つの変数の位置を取得してしまうのです。こうすることにより、CSaveManagerクラスは、メモリブロックのポインタ(m_pMemBlock)からそのアドレスを、メモリサイズの変数(m_iMemSize)からそのサイズを間接的に知る事ができるようになります。
上のメモリブロックを指定するマクロは次のように定義できます。
#define DATA_MEM( CLASSNAME, POINTERNAME, SIZENAME ) \
{\
TYPE_MEM, \
( (__int64)&((CLASSNAME*)0)->POINTERNAME ), \
( (__int64)&((CLASSNAME*)0)->SIZENAME ) \
}
使い方はこうです。
// 保存する変数の位置を指定
DATARECORD CPlayer::m_gDataRecord[] =
{
DATA_MEM( CPlayer, m_pMemBlick, m_pMemSize )
};
B セーブロードは簡単
Aのようにサイズを指定してしまえば、セーブもロードはとっても簡単に行えます。セーブする時には指定のサイズ分ポインタからデータを抜き取れば良いだけですし、ロード時には指定分のヒープメモリを確保して、そこにデータを流し込めばいいんです。非常に簡単ですから、プログラムの説明は割愛します(^-^;
C ケチらずにサイズ変数を使おう
メモリブロックのサイズを指定するための変数を用意することで、不定長のメモリについてセーブロードができるようになりました。CSaveObjBaseクラスから派生されないメンバ変数のセーブロードは、基本的に復元するためのサブ変数を用いて間接的に再構築させていきます。例えばSTLのlistやvectorを復活させる場合などもそうです。テクスチャのファイル名を格納する変数を宣言しておけば、そのファイル名をロードすることでテクスチャを戻す事ができます。アニメーションセットなどもわざわざ行列自体をセーブする必要は無く、xファイル名と動作トラック番号・動作時間だけ保存して再構築すれば良いわけです。
自己復活用の変数はどしどし使って良いと思います。最近はPCであれコンシューマ機であれ、メモリは結構ありますから、クラスの宣言で変数をケチる必要はあまり無いと思います。それによりセーブロードが楽になるのでしたら、しめたものでしょう。
D 次の課題は派生クラスのメンバ変数差分保存
次の課題はメンバ変数の差分保存です。それがどういうことか、例えば次のようなクラス宣言をご覧下さい。
class CPlayer : public CSaveObjBase
{
protected:
int m_iLevel;
int m_bFlag;
static DATARECORD m_gDataRecord[];
};
class CPlayerEx : pblic CPlayer
{
protected:
int m_iHitPoint;
static DATARECORD m_gDataRecord[];
};
CPlayerはCSaveObjBaseクラスから派生されていますので、自動セーブロード機能をつける事ができます。これは、これまで説明してきたことをすれば良い訳です。では、CPlayerクラスから派生されているCplayerExクラスのm_gDataRecord配列はどのように定義すれば良いのでしょうか?考えられるのは次のような設定です。
DATARECORD CPlayerEx::m_gDataRecord[] =
{
DATA_LOCAL( CPlayerEx, m_iLevel ),
DATA_LOCAL( CPlayerEx, m_bFlag ),
DATA_LOCAL( CPlayerEx, m_iHitPoint ),
DATA_END
};
これは可能ですし、ちゃんと機能もします。ただ、派生の度に親クラスのメンバ変数全て書き直さなければならないと言うのは、なんとも面倒な話ですし、第一書き漏らしでもしたら大変です。これは保守性に乏しいと言えます。できることならば、親クラスの事は気にせずに、
DATARECORD CPlayerEx::m_gDataRecord[] =
{
DATA_LOCAL( CPlayerEx, m_iHitPoint ),
DATA_END
};
と、派生クラスで追加したメンバ変数分だけ追加すれば良い形式にしたいものです。
次の章ではこの「派生オブジェクトのセーブ」を楽にする実装を考えて見ます。これで、ほぼ基本的な機能が全て揃うことになりますから、がんばりましょう〜!