C++で遊ぼう
「静的仮想関数」編

しのはらのC++実験室トップページ

コンストラクタで仮想関数を呼びたいと思いませんか

 仮想関数とは、オブジェクトの実際のクラスを動的に判断して オーバーライドされた適切なメンバ関数を呼び出す機能ですね。 派生クラスで仮想関数をオーバーライドする目的は一般に 「基底クラスの動作をカスタマイズすること」にあると思います。
 これをコンストラクタの中でも使用したくなることがありませんか?  つまり、オブジェクトの生成過程をカスタマイズできるようにしたいわけです。 そのためには「コンストラクタの中から仮想関数を呼び出せばいい」 と考えるのが普通です。例えば、
// うまく行かない例
#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が指すオブジェクトは まだAppleBananaになりきれていないので、 Fruitであると見なされているわけです。
 このように、極めて自然な要求に見える「生成過程のカスタマイズ」ですが、 C++ではこれを仮想関数を使って自然に実現することができないのです。 MicrosoftのMFCやBorland C++のOWLを使ったことがある人なら 分かるかも知れませんが、こういう場合 コンストラクタですべての生成処理を済ませることをあきらめて、 「生成用のメンバ関数」というのを用意して、
// 処理を二段階に分ける例(イメージ)
CMyWindow myWindow;   // CMyWindowはCWndの派生クラス
myWindow.Init();      // InitはCWndのメンバ関数
のようにする方法があります。 メンバ関数Init()の中で実際の生成処理を行うので、仮想関数によるカスタマイズが可能になります。 しかし、僕はこの方法はあまり好きではありません。場合によっては避けたいと思うことがあります。 クラス内部にInit前とInit後という2つの状態が発生するので、それを管理する手間が増えますし、使い手にとってはメンバ関数Init()の呼び忘れの心配もあります。 なにより、こうやって仕方なく設計を変えるのって、なんとなく「負けた」気がしませんか。
 あきらめてはいけません。

欲しかったのは「静的仮想関数(static virtual function)」

 やりたいことは、次のように分析できます。  さて、静的関数であってしかも仮想関数のように振る舞う、 そのような関数の実現方法を示しましょう。

カスタマイズする関数が1つの場合

実は、分かってしまえばとても単純なことなのです。
// カスタマイズする関数が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( &param1 );
	
	Apple::SV param2("青");
	Apple b( &param2 );
}

実行結果
私はりんごです。色は赤です。
私はりんごです。色は青です。

さて、どうやら方法は見つかりました。
ところで、ここまで手間をかけてまで実現したいことなのか、と言われるとちょっと苦しいかも知れません。
まあ、そういうコンセプトのページですから良いですね。