クラスのメンバ変数のオフセット値を取得してしまおう!
今例として次のような簡単なクラスを作成します。
class CPlayer
{
public:
int m_iLevel;
char m_bFlag;
int m_iHitPoint;
};
このクラスから作成したオブジェクトに対して、次のように値を代入します。
CPlayer P1;
P1->m_iLevel = 20;
「->」は選択演算子とかアロー演算子などと呼ばれます。この代入の方法は便利ですよね。ところで、「->」が指している場所はいったい何なのでしょうか?それを確認するために、次のようなプログラムを作成してみましょう。
CPlayer *pP1 = new CPlayer;
int cls_address = (int)pP1;
int level_address = (int)&(pP1->m_iLevel);
int flag_address = (int)&(pP1->m_bFlag);
int hp_address = (int)&(pP1->m_iHitPoint);
pP1にCPlayerのオブジェクトへのポインタを格納します。(int)pP1はそのポインタを整数型にキャストしています。(pP1->miLevel)というのは変数そのものを指しますが、&(pP1->miLevel)とするとそのアドレスを指すことになります。ですから(int)&(pP1->miLevel)とすると、m_iLevelの変数を格納するアドレスを整数に変換していることになります。ということで、上のプログラムはオブジェクトpP1の変数の場所をそれぞれの変数に格納するプログラムとなります。
さて、私の環境で、上のプログラムの結果は次のようになりました。
変数 アドレス cls_addressからのオフセット cls_address 3844368 level_address 3844368 0 flag_address 3844372 4 hp_address 3844376 8
cls_addressとlevel_addressが同じアドレスであることに注目してください。そして、宣言順に変数が並んでいることもオフセットを見るとわかります。このプログラムからアロー演算子によって、オブジェクト内の変数の位置がちゃんと指されている事が確認できますね。
オフセットの値はpP1からの相対値です。ということは、もしpP1=0であれば、上のプログラムはダイレクトに変数のオフセット位置を得る事ができます。これは次のようなプログラムになります。
int Ofs_level = (int)&((CPlayer*)0)->m_iLevel;
int Ofs_flag = (int)&((CPlayer*)0)->m_bFlag;
int Ofs_hp = (int)&((CPlayer*)0)->m_iHitPoint;
出力される値は次の通りです。
型 変数 アドレス(=オフセット値) int Ofs_level 0 char Ofs_flag 4 int Ofs_hp 8
これがいったい何の役に立つのか?実はセーブ・ロードに一役買います。オブジェクトのポインタからこのオフセット順にデータをファイルに落とせばセーブ、逆に空オブジェクトに対してファイルに保存しておいた変数を流し込めばロードが出来てしまいます(もちろん、public宣言されていない変数にはアクセスできませんが、そこもうまいカラクリがちゃんとあります)。
クラス内の変数のオフセット位置を取得するマクロを作っておくと便利です。以下のように設定します。
#define CLSMEMBEROFS( CLASSNAME, MEMBERNAME ) ((int)&((CLASSNAME*)0)->MEMBERNAME)
ちなみに、通常メンバ関数のアドレスは取得できません。ただし、static宣言された場合には可能です。それはオフセット位置ではなくて、実行時に決められる固定アドレスです。