// うまく行かない例
#include <iostream>
class Fruit{
public:
virtual const char* name(){return "果物";}
Fruit(){
std::cout << "私は" << name() << "です。\n";
}
};
class Apple : public Fruit{
public:
virtual const char* name(){return "りんご";}
};
class Banana : public Fruit{
public:
virtual const char* name(){return "バナナ";}
};
// テスト
int main()
{
Apple a;
Banana b;
}
このようにするでしょう。
しかし、この方法では期待する結果は得られません。
私は果物です。
私は果物です。
こうなってしまう理由は、基底クラス(この場合Fruit
クラス)の
コンストラクタを実行しているときには派生クラス
(この場合Apple
クラスやBanana
クラス)
のオブジェクトはまだ初期化が終わっていないからです。
まだ存在していない、とも言えます。
Fruit
コンストラクタの実行時点では、
this
が指すオブジェクトは
まだApple
やBanana
になりきれていないので、
Fruit
であると見なされているわけです。
// 処理を二段階に分ける例(イメージ)
CMyWindow myWindow; // CMyWindowはCWndの派生クラス
myWindow.Init(); // InitはCWndのメンバ関数
のようにする方法があります。
メンバ関数Init()の中で実際の生成処理を行うので、仮想関数によるカスタマイズが可能になります。
しかし、僕はこの方法はあまり好きではありません。場合によっては避けたいと思うことがあります。
クラス内部にInit前とInit後という2つの状態が発生するので、それを管理する手間が増えますし、使い手にとってはメンバ関数Init()の呼び忘れの心配もあります。
なにより、こうやって仕方なく設計を変えるのって、なんとなく「負けた」気がしませんか。すなわち「仮想関数」と同じようなことをしたいのです。
基底クラスのコンストラクタ内で呼び出すのですから、 その時、派生クラスの非静的メンバはまだ初期化されていません。 なので、派生クラスのための関数とは言っても派生クラスの 非静的メンバにはアクセスすることが許されません。 すなわちこの関数は「非静的メンバにアクセスしない関数」ですから、 「静的関数」と考えるのが自然です。
// カスタマイズする関数が1つの場合
#include <iostream>
class Fruit{
protected:
// 基底クラスは静的仮想関数へのポインタを保持する。
typedef const char* StaticVirtualFunc();
StaticVirtualFunc * const name;
public:
static const char* Name(){return "果物";}
Fruit(StaticVirtualFunc *a_name = Name):name(a_name){
std::cout << "私は" << name() << "です。\n";
}
};
class Apple : public Fruit{
public:
static const char* Name(){return "りんご";}
Apple(StaticVirtualFunc *a_name=Name):Fruit(a_name){}
};
class Banana : public Fruit{
public:
static const char* Name(){return "バナナ";}
Banana(StaticVirtualFunc *a_name=Name):Fruit(a_name){}
};
// テスト
int main()
{
Apple a;
Banana b;
}
これで期待する結果を得ることが出来ます。
私はりんごです。
私はバナナです。
説明はほとんど必要ないくらい、やってることは単純だと思います。
クラス毎に呼び出すべき関数を切り換えるために、
関数ポインタを派生クラスから基底クラスへと伝えているのです。
その関数ポインタを使うことで、
基底クラスのコンストラクタは派生クラスが指定する適切な関数を
呼び出すことができるわけです。
// カスタマイズする関数が複数の場合
#include <iostream>
#define BEGIN_DECLARE_STATIC_VIRTUAL_BASE(SV) \
struct SV{
#define END_DECLARE_STATIC_VIRTUAL_BASE(SV) \
static SV *GetObj(){static SV o;return &o;} \
};
#define BEGIN_DECLARE_STATIC_VIRTUAL(SV,T_BASE) \
struct SV : T_BASE::SV{
#define END_DECLARE_STATIC_VIRTUAL(SV,T_BASE) \
static SV *GetObj(){static SV o;return &o;} \
};
class Fruit{
protected:
// 静的仮想関数の基底クラス
BEGIN_DECLARE_STATIC_VIRTUAL_BASE(SV)
virtual const char* name(){return "果物";}
virtual const char* taste(){return "甘い";}
END_DECLARE_STATIC_VIRTUAL_BASE(SV)
SV * const sv;
public:
Fruit(SV* asv=SV::GetObj()) :sv(asv)
{
std::cout << "私は" << sv->taste() << sv->name() << "です。\n";
}
};
class Apple : public Fruit{
protected:
// Appleの静的仮想関数
BEGIN_DECLARE_STATIC_VIRTUAL(SV,Fruit)
virtual const char* name(){return "りんご";}
virtual const char* taste(){return "甘酸っぱい";}
END_DECLARE_STATIC_VIRTUAL(SV,Fruit)
public:
Apple(SV* asv=SV::GetObj()) :Fruit(asv){}
};
class Banana : public Fruit{
protected:
// Bananaの静的仮想関数
BEGIN_DECLARE_STATIC_VIRTUAL(SV,Fruit)
virtual const char* name(){return "バナナ";}
virtual const char* taste(){return "柔らかくて甘い";}
// 新しい関数を追加することもできます。
virtual const char* comment(){return "釘は打てません。";}
END_DECLARE_STATIC_VIRTUAL(SV,Fruit)
SV * const sv;
public:
Banana(SV* asv=SV::GetObj()) :Fruit(asv),sv(asv)
{
std::cout << sv->comment() << "\n";
}
};
int main()
{
Apple a;
Banana b;
}
実行結果
私は甘酸っぱいりんごです。
私は柔らかくて甘いバナナです。
釘は打てません。
静的仮想関数の動作にパラメータを与えたい場合には、次のようにします。
// 静的仮想関数の動作にパラメータを与えたいの場合
#include <iostream>
#define BEGIN_DECLARE_STATIC_VIRTUAL_BASE(SV) \
struct SV{ \
SV(){}
#define END_DECLARE_STATIC_VIRTUAL_BASE(SV) \
static SV *GetObj(){static SV o;return &o;} \
};
#define BEGIN_DECLARE_STATIC_VIRTUAL(SV,T_BASE) \
struct SV : T_BASE::SV{ \
SV(){}
#define END_DECLARE_STATIC_VIRTUAL(SV,T_BASE) \
static SV *GetObj(){static SV o;return &o;} \
};
class Fruit{
protected:
// 静的仮想関数の基底クラス
BEGIN_DECLARE_STATIC_VIRTUAL_BASE(SV)
virtual const char* name(){return "果物";}
virtual const char* color(){return "不明";}
END_DECLARE_STATIC_VIRTUAL_BASE(SV)
SV * const sv;
public:
Fruit(SV* asv=SV::GetObj()) :sv(asv)
{
std::cout << "私は" << sv->name() << "です。"
"色は" << sv->color() << "です。\n";
}
};
class Apple : public Fruit{
public:
// Appleの静的仮想関数
BEGIN_DECLARE_STATIC_VIRTUAL(SV,Fruit)
const char* color_;
SV(const char* acolor) :color_(acolor){}
virtual const char* name(){return "りんご";}
virtual const char* color(){return color_;}
END_DECLARE_STATIC_VIRTUAL(SV,Fruit)
Apple(SV* asv=SV::GetObj()) :Fruit(asv){}
};
int main()
{
Apple::SV param1("赤");
Apple a( ¶m1 );
Apple::SV param2("青");
Apple b( ¶m2 );
}
実行結果
私はりんごです。色は赤です。
私はりんごです。色は青です。
さて、どうやら方法は見つかりました。