C++はオブジェクト指向言語なので、言うまでも無くポリモーフィズム(多態性)をサポートしています。ここで言うポリモーフィズムとは継承を用いた動的なものを指しますが、C++ではテンプレート機能を使用することで、静的なポリモーフィズムを実現することもできます。今回はそれに関するお話。
struct base { // コンストラクタとかはいろいろ略 virtual void Execute() = 0; }; struct A : public base { virtual void Execute() { std::cout << "hello, "; } }; struct B : public base { virtual void Execute() { std::cout << "world" << endl; } }; int main() { base* object = new A(); object->Execute(); // A::Execute()が呼ばれる delete object; object = new B(); object->Execute(); // B::Execute()が呼ばれる delete object; return 0; }
一般に言うポリモーフィズムは上記のような感じで実現されていると思いますが、このコードだと仮想関数を使用する分メモリ使用量がvtableの分増えますし、パフォーマンスにも(微々たるものですが)影響が出てしまいます。そこでポリモーフィズムを動的ではなく静的(コンパイル時)に解決できるところではしてしまおうという目論見が以下。
struct A { void Execute() { std::cout << "hello"; } }; struct B { void Execute() { std::cout << "world" << endl; } }; template<class T> struct Executer { T m_object; void Execute() { m_object.Execute(); } }; int main() { Executer<A>().Execute(); // 内部でA::Execute()が呼ばれる Executer<B>().Execute(); // 内部でB::Execute()が呼ばれる return 0; }
言ってしまえば継承を委譲に変更しただけですが、baseクラスが無くなった事でクラス間の依存性も無くなりました。但しこの方法だと、AとBのクラスを実行時に切り替えるなどといったことができなくなりますので、動的に行うものを何でもかんでも静的なものに置き換えられるわけではないことに注意。あくまでコンパイル時に全てが決まっている時にのみしか使用できません。多態というにはちょっとしょぼいかも。
パフォーマンスを気にしないで、クラス間の依存関係だけ取り除きたい場合にこの制限は確かにしょぼく感じており不満があったのですが、ネットを巡回してて見つけたきまぐれ日記: C++ で多態を見て目から鱗。継承と委譲を両方使ったコードが例示されていますが、これを使えば先の不満を解消することができます。この方法は思いつきませんでした。まだまだ修行が足りません……。
// クラスA,Bの定義は二番目の例と同じです struct IExecuter { virtual void Execute() = 0; }; template<class T> struct Executer : public IExecuter { T m_object; void Execute() { m_object.Execute(); } }; int main() { Executer<A> a; Executer<B> b; IExecuter* obj = &a; obj->Execute(); // A::Execute()が呼ばれる obj = &b; // 二番目の例だとこれができない obj->Execute(); // B::Execute()が呼ばれる return 0; }
やはり例が良くないので、もう少しなんとかしたいところです。しかし、これを使えば昔書いた屁みたいなコードがだいぶすっきりするな……でも他の人も手を入れ始めているし、あんまり思い切った変更はしないほうがいいか……。