通常のオーバーロードや仮想関数のオーバーライドの場合は全ての関数が使用・未使用に関わらずコンパイルされる.
特に仮想関数を用いたオーバーライドは,その時のポインタが指す実体によって,どの関数が呼ばれるか動的に決定される.
一方,テンプレートはコンパイル時にテンプレート引数によって静的に生成される関数が決定される.
テンプレートは機能は概ね以下の通りである.
- 型に対して一般的なアルゴリズムを定める.すなわちアルゴリズムの使い回しができる.
- テンプレート引数に関数オブジェクト指定することで,アルゴリズムの交換ができる.
- 派生に相当する実装の変更手段として特殊化が可能である.
- テンプレート自体はコンパイルできないため,公開せざるをえない.ただし,特定の型の場合のみを明示的インスタンス化することでコードを公開せずにライブラリとすることはできる.
またテンプレートの柔軟な仕様を駆使することで様々なテクニックが発見(?)・提案されている.
通常テンプレートはヘッダー内にコードを書いていく.それは使用する全てのソースで全てのテンプレートがみえていなければ,実体化できないからである.
しかし,使うものを明示的インスタンス化することでテンプレートの実装を通常のソースコードのようにヘッダーと分離することが可能となる.
関数テンプレートは明示的インスタンス化することができないが、ダミー関数内でインスタンスを宣言すればよい。
一方,int, double, std::string以外のテンプレート引数を指定した場合はfuncが未解決のエラーとなる.
しかし,使うものを明示的インスタンス化することでテンプレートの実装を通常のソースコードのようにヘッダーと分離することが可能となる.
関数テンプレートは明示的インスタンス化することができないが、ダミー関数内でインスタンスを宣言すればよい。
//Sample.hpp template <typename T> void SomeFunc(T val); template <typename T> class Sample { public: void func(T val); } //Sample.cpp template<typename T> void SomeFunc(T val) { //実装 } template<typename T> void Sample<T>::func(T val) { //実装 } //明示的インスタンス化 template class Sample<int>; template class Sample<double>; template class Sample<std::string>; //ダミー関数によるインスタンス化 void Dummy() { SomeFunc<int>( 0 ); SomeFunc<double>( 0.0 ); SomeFunc<std::string>( "foo" ); }この場合,Sample.cppをコンパイルした時に,Sample<int>, Sample<double>, Smaple<std::string>は実体化される.そのため外部のファイルからでもこれらのクラスを利用することが可能である.
一方,int, double, std::string以外のテンプレート引数を指定した場合はfuncが未解決のエラーとなる.
テンプレート引数として受けるクラスや関数オブジェクトに対して,共通点はあるが派生関係にない場合,その共通点を満たすかどうかをチェックしたいことがある.
C++0xではconceptの導入が検討されたが,既存のコード(非コンセプト)とコンセプトを用いたコードの共存などが問題となり先送りされた.
また,boostでテンプレート引数で指定された型がコンセプトを満たすかチェックするライブラリが提供されている.
簡単な手法としては以下のような方法が考えられる.
C++0xではconceptの導入が検討されたが,既存のコード(非コンセプト)とコンセプトを用いたコードの共存などが問題となり先送りされた.
また,boostでテンプレート引数で指定された型がコンセプトを満たすかチェックするライブラリが提供されている.
簡単な手法としては以下のような方法が考えられる.
//definition struct hoge_concept {}; struct piyo_concept {}; struct hoge { typedef hoge_concept is_hoge; void operator()() { std::cout << "hoge" << std::endl; } }; struct piyo { typedef piyo_concept is_piyo; void operator()() { std::cout << "piyo" << std::endl; } }; template<typename hoge_type> void FuncForHoge(hoge_type h) { typedef hoge_type::is_hoge concept_check; } template<typename piyo_type> void FuncForPiyo(piyo_type h) { typedef piyo_type::is_piyo concept_check; } //Sample Usage FuncForHoge( hoge() ); //OK FuncForHoge( piyo() ); //NG: "is_hoge is not member of piyo" FuncForPiyo( hoge() ); //NG: "is_piyo is not member of hoge" FuncForPiyo( piyo() ); //OK
boost::operatosに代表される非常に多くのライブラリで使われる手法である.
基本クラスが派生クラスをテンプレート引数の取ることで,派生クラスの型を用いる関数を定義することができる.
以上のように,基本クラスの異なる複数のクラスで同一のコードが存在する場合は,CRTPによって簡略化することが可能である.
template<class T> struct Addable { T operator+(const T& other) { T tmp(*this); return tmp += other; } }; class Matrix3x3 : public Addable<Matrix3x3> { public: Matrix3x3 operator+=(const Matrix3x3& other); };
基本クラスが派生クラスをテンプレート引数の取ることで,派生クラスの型を用いる関数を定義することができる.
以上のように,基本クラスの異なる複数のクラスで同一のコードが存在する場合は,CRTPによって簡略化することが可能である.
CRTPを用いると仮想関数を用いずに,すなわちRTTIによる実際の型の解決をせずに同様の機能を実現できる.これはわずかにパフォーマンス上有利である.
一方でCallXXXFuncは型の種類の数だけコンパイル時に生成される.
一方でCallXXXFuncは型の種類の数だけコンパイル時に生成される.
class virtual_base { public: virtual void Func()=0; }; class virtual_derived1 : public virtual_base { public: virtual void Func() { std::cout << "virtual_derived1" << std::endl; } }; class virtual_derived2 : public virtual_base { public: virtual void Func() { std::cout << "virtual_derived2" << std::endl; } }; void CallVirtualFunc(virtual_base& b) { b.Func(); } int main() { virtual_derived1 derived1; virtual_derived2 derived1; CallVirtualFunc(derived1); CallVirtualFunc(derived2); returnn 0; }
template <typename derived> class generic_base { public: //derivedの型にこれは確実に変換可能であるからstatic_castでよい.そのため実行時のコストはないと言える. void Func() { static_cast<derived*>(this)->Func(); } }; class generic_derived1 : generic_base<generic_derived1> { public: void Func() { std::cout << "generic_derived1" << std::endl; } }; class generic_derived2 : generic_base<generic_derived2> { public: void Func() { std::cout << "generic_derived2" << std::endl; } }; template <typename derived_class> void CallGenericFunc(generic_base<derived_class>& b) { b.Func(); } int main() { generic_derived1 d1; generic_derived1 d2; CallGenericFunc(d1); CallGenericFunc(d2); return 0; }
このCallGenericFuncは実はわざわざgeneric_base<T>を引数とせずとも直接derived_classを入れてもよいように感じられる.
すなわち,
template<typename T> void CallDerivedFunc(T t) { t.Func(); }
としても問題は無い.しかしながらこの場合のCallDerivedFuncはあらゆるクラスを受け入れてしまう.
一方上記例ではgeneric_base派生クラスしか受け付けない.すなわちCRTPとすることでテンプレートでありながら受け入れる型をgeneric_base派生クラスに限定することができている.
コメントをかく