template method patternをインライン展開させてみる
template method patternとは、wikipediaによると「ある処理のおおまかなアルゴリズムをあらかじめ決めておいて、そのアルゴリズムの具体的な設計をサブクラスに任せること」である。
具体例として、画像処理のシードフィルアルゴリズムでの例を昔トピックとして書いたのでそちらを参照してください。
http://d.hatena.ne.jp/n-trino/20120914#p1
さてこのパターン、割と便利でいろいろなところで使っているのだが一つ欠点がある。それは継承してオーバーライドした関数を呼ぶので重いのだ。特に上記シードフィルアルゴリズムのような画像処理の場合、ピクセル単位で呼んでいるので結構バカにならない。というわけでインライン展開されるような実装が好ましいのだ。そういうわけで、template method patternの利便性を維持したまま高速化する方法を考えてみました。
とりあえず、高速化前のサンプルコードを置いておきます。
class Base{ public: void Execute(){ Function(); } protected: virtual void Function() = 0; }; class A: public Base{ protected: virtual void Function(){ std::cout << "A" << std::endl; } }; class B: public Base{ protected: virtual void Function(){ std::cout << "B" << std::endl; } }; { A a; a.Execute(); B b; b.Execute(); }
まず最初に誰もが思いつくのが関数オブジェクトを使う方法だろう。
class Base{ public: template< class Funct > void Execute( const Funct& func ){ func(); } }; struct A{ void operator()()const{ std::cout << "A" << std::endl; } }; struct B{ void operator()()const{ std::cout << "B" << std::endl; } }; { Base b; b.Execute( A() ); b.Execute( B() ); }
これで無事インライン展開されて高速化されて無事達成・・・と言いたいところだが一つ欠点がある。それは基底クラス側のメンバの参照が関数オブジェクトからできないこと。というわけで、継承という形は維持してほしいのだ。
ところで、オーバーライドされた仮想関数を高速化する裏技をご存知だろうか?簡単に説明すると無理やりアップキャストして無理やり継承先の関数を呼ぶのだ。
Base* b = new A(); b->Execute();//←遅い... ((A*)b)->A::Execute();//←速い!
もちろんこの方法だとポリフォーリズムの意味が全くなくなるので実用的には疑問な技なのだが、今回はtemplate method patternなのでインターフェースとしての働きはないし特に問題はないのだ。というわけでこの裏技を組み込んでみる。
template< class Inhr > class Base{ public: void Execute(){ ((Inhr*)this)->Inhr::Function(); } protected: virtual void Function() = 0; }; class A: public Base< A >{ template< class > friend class Base; protected: virtual void Function(){ std::cout << "A" << std::endl; } }; class B: public Base< B >{ template< class > friend class Base; protected: virtual void Function(){ std::cout << "B" << std::endl; } }; {//使い方は同じ A a; a.Execute(); B b; b.Execute(); }
というわけで今度こそうまくいったわけだ。なおオーバーライドしたのを直接呼んでいる都合上、protectedが効いているのでfriend指定が必要です。めんどくさければpublicにすればいいだけですが、template method patternの設計的にpublicにするのはどうなのかな?と個人的に思います。
しかしこれ、もはや仮想関数でオーバーライドする意味なくね?無理やりキャストして呼んでいるんだからダックタイピングでよくね?というわけでさらに改良(改悪?)
template< class Inhr > class Base{ public: void Execute(){ ((Inhr*)this)->Function(); } }; class A: public Base< A >{ template< class > friend class Base; protected: void Function(){ std::cout << "A" << std::endl; } }; class B: public Base< B >{ template< class > friend class Base; protected: void Function(){ std::cout << "B" << std::endl; } }; {//使い方はやはり同じ A a; a.Execute(); B b; b.Execute(); }
大分すっきりしました。
無理やりアップキャストしたりダックタイピングやったりと色々と危なそうな実装ですが当初の目的はなんとか果たしました。
さて、製品に組み込んだらコードレビューで何を言われるかな・・・(´・ω・`)