C++学習ノート(七)テンプレートと汎用プログラミング
6613 ワード
テンプレートの概要
テンプレートはC++の非常に重要な特性であり、C++汎用プログラミングの基礎である.一部のC++に対して極度の偏見を持っている人は、テンプレートがC++のこの世界に対する唯一の貢献であるとさえ言っている(もちろん、私は賛成していない)、テンプレートのC++における重要性を見ることができ、STL全体がテンプレートに基づいており、その応用が広範であることがわかる.
C++テンプレートの導入の重要な原因の1つはアルゴリズムの再利用であり、例えば次の例である.
使用方法も簡単です.直接呼び出すと、賢いコンパイラがテンプレートのパラメータタイプを自動的に導出します.
テンプレートはC++の非常に重要な特性であり、C++汎用プログラミングの基礎である.一部のC++に対して極度の偏見を持っている人は、テンプレートがC++のこの世界に対する唯一の貢献であるとさえ言っている(もちろん、私は賛成していない)、テンプレートのC++における重要性を見ることができ、STL全体がテンプレートに基づいており、その応用が広範であることがわかる.
C++テンプレートの導入の重要な原因の1つはアルゴリズムの再利用であり、例えば次の例である.
bool mless_than(const int& v1, const int& v2) {
return v1 < v2;
}
プログラムは簡単です.最初のパラメータが2番目のパラメータより小さいかどうかを比較するだけです.このアルゴリズムは私たちのプログラムで非常によく見られると言えます.これはintバージョンです.もしstringバージョン、doubleバージョン、カスタムクラスバージョンが必要ならどうすればいいですか.テンプレートがなければ、コードが同じであっても、ソートアルゴリズムのようなアルゴリズムが長い場合、このような複数のバージョンを書くのは冗長で退屈なことであり、将来どのようなタイプのカスタムアルゴリズムが必要かは予見できません.アルゴリズムライブラリを書く人も、使用者がどのようなタイプを定義するか分からない.テンプレートの必要性を話して、今その実現を見てみましょう.それともさっきの関数ですか.template
bool mless_than(const T& v1, const T& v2) {
return v1 < v2;
}
使用方法も簡単です.直接呼び出すと、賢いコンパイラがテンプレートのパラメータタイプを自動的に導出します.
bool result = mless_than(2.8, 4.1);//double version
在某些情况下,编译无法从调用参数推到出所有的模板类型,或是我们传入的参数类型不是我们希望用于实例化函数的模板参数类型时,我们也可以手动的指定模板参数类型,调用方式如下:bool result = mless_than
(2.8, 4.1);//double version
テンプレートは、関数に加えてクラスでも使用できます.たとえば、次のようになります.template
class A{ //...other definition private: T v; //...other definition }
STLをよく使うなら、使い方はもう慣れているはずです.A
a;
これはintバージョンのクラスAでオブジェクトaを定義する.
注意しなければならないのは、関数テンプレートでもクラステンプレートでも、それらは本当の関数やクラスではありません.コンパイラに本当の関数インスタンスやクラスインスタンス(ここではオブジェクトではありませんよ)をどのように生成するかを教えているだけです.つまり、Aはクラスではありません.Aはクラスです.
テンプレートの概要についてはここまでにしましょう.この初歩的な概念があれば、classとtypenameの違いを見てみましょう.
classとtypename
上のテンプレート定義ではtypenameキーワードを使用してテンプレートパラメータを定義していますが、以前にテンプレートを学習したり理解したりした学生は、もう一つのキーワードclassがここでテンプレートパラメータを定義するために再利用されていることに気づくかもしれません.では、それらにはいったい上記の違いがありますか?答えはテンプレートパラメータを定義することです.ここでは区別はありません.typenameは後で導入されるキーワードなので、古いコードの中にはclassキーがもっと一般的になる可能性があります.
私はそれらがここで区別がないと言ったが、私が単独で小さなタイトルを並べた以上、この中にはまだ少し内容があることを説明した.typenameはテンプレートにいくつかの他の役割を果たしています.これについて話す前に概念を理解しましょう
:nested dependent type names(ネスト依存タイプ名).次の定義を考慮します.のうち、c::key_type*ptrはどういう意味ですか?STLに詳しいなら、c::key_を利用していると言えるかもしれません.typeはポインタptrを定義し、mapとsetにはこのタイプがあり、パッケージされたキーのタイプを表しています.しかし、しかし、もし誰かがクラスをカスタマイズし、彼がちょうどこのクラスの内部でkey_という名前を定義したらtypeの静的変数であれば,この文は変数定義ではなく乗算である.では、コンパイラはこの文をどのように見ているのでしょうか.まず、脱出依存タイプ名という概念についてお話しします.このようにクラス内部に定義されたタイプですが、外部クラスのタイプはテンプレートパラメータに依存します.いわゆる脱出依存タイプ名です.デフォルトでは、コンパイラはクラス名ではなく変数名として扱われます.つまり、デフォルトでは、コンパイラは乗算として扱われます.コンパイラにタイプ名として扱わせたい場合は、typenameキーワードを付けて修飾できます.template
void test(const T& c) { T::key_type *ptr; //other implementation } typename c::key_type *ptr;
このような脱出依存型名については,typenameキーワードを付けて修飾すべきである.ただし、たとえばベースクラスリストでネスト依存タイプが使用される場合はtypenameキーワードは使用されません.ここに表示されるマーカーはタイプ名のみである可能性があるためです.
Notypeテンプレート
前述したのはすべてタイプテンプレートであり,実際にはC++のテンプレートメカニズムは非タイプテンプレートもサポートしている.それが何であるかをより直感的に説明するために、STLで実際に使用されている例を見てみましょう.それはビットマップクラスです.bitset<32> b;
上記の定義は、32サイズのビットマップクラスを定義し、非タイプパラメータを使用してサイズを指定することである.非タイプテンプレートの簡単な実装を見てみましょう.template
class A{ //some definition int data[SIZE]; }
非タイプテンプレートパラメータは、ここでA内部で維持される配列変数のサイズを指定する役割を果たすことがわかります.パラメータの転送を行う場合、非タイプのパラメータは、次のような場合に便利です.template
void array_init(T (&parm)[N]) { for (size_t i = 0; i != N; ++i) { parm[i] = 0; } }
この関数の役割は、任意のサイズの配列を初期化することであり、ここでは、伝達される配列参照のサイズを非タイプパラメータで巧みに指定します.
テンプレートの特化と偏特化
テンプレートの役割は、任意のタイプに統一された実装方法、またはアルゴリズムロジック、またはデータ構造を提供することです.しかし、いくつかのタイプでは、統一された実装方式が私たちの要求を満たすことができない場合があります.最初のテンプレート関数を考慮すると、定数文字列を渡すと、コンパイラはこのような関数コードをインスタンス化します.bool mless_than(const char* const& v1, const char* const& v2) { return v1 < v2; }
コンパイル実行には問題がありますが、重要なのは、文字列自体ではなく2つの文字列のアドレスを比較することです.これは明らかに私たちが望んでいるものではありません.この場合、テンプレートの特化機能を利用して、C_を処理するために特別なバージョンをカスタマイズする必要があります.styleの文字列、特化の実現方式は以下の通りである.template<> bool mless_than(const char* const& v1, const char* const& v2) { return strcmp(v1, v2) < 0; }
私たちが伝えると
C_styleの文字列の場合、コンパイラはテンプレートを利用して生成するのではなく、私たちが特化したこのバージョンを呼び出します.
テンプレートの特化はクラステンプレートにも使用できます.特化したクラスでは、元のテンプレートの定義に従う必要はありません.クラステンプレートの特化定義は関数テンプレートとほぼ似ていますが、クラス名の後ろに特化されたテンプレートパラメータタイプを指定する必要があります.
テンプレートの特化に加えて、C++はクラステンプレートを特化することができます(関数テンプレートはできません).これは、一部のテンプレートパラメータのみを特化することです.たとえば、次のようにします.template
class A{ //other definition T d1; V d2; }; template class A { //other definition T d1; int d2; };
オフセット化では,テンプレートパラメータVをintに特化したが,オフセット化後のテンプレートは実際のクラスではなくテンプレートクラスであることに注意しなければならない.
テンプレートメタプログラミング
テンプレートメタプログラミングとは実際には新しいC++特性を導入していないが,C++非タイプテンプレートとテンプレート特化の非常に奇妙な使い方である.実行時に計算されたタスクの一部をコンパイル期間に配置して完了し、実行効率を向上させることができます.たとえば、静的配列のサイズとして定数の乗算を使用すると、テンプレート要素を使用してプログラミングできます.template
class Factorial { public: unsigned VALUE = N*Factorial ::VALUE; }; template<> class Factorial<0> { public: unsigned VALUE = 1; };
上のテンプレートクラスFactorialは階乗を計算するために使用されており、コンパイラで再帰を巧みに利用して必要な階乗値を計算することができます.注意すべきは、ここの再帰出口は偏特化テンプレートであり、不思議でしょう.
テンプレートのコンパイルメカニズム
テンプレートの基本的な特性と使用方法について話した後、最後にテンプレートのコンパイルメカニズムを見てみましょう.テンプレートは、コンパイラがインスタンスを生成するためのコンパイラを提供する方法にすぎないことを知っています.テンプレートは必要に応じてインスタンス化されます.つまり、クラステンプレートの定義をヘッダファイルに配置し、メンバー関数の実装をソースファイルに配置すると、定義されたソースファイルをコンパイルするときにコードが生成されません.これにより、他の使用ファイルには器クラスの定義のみが含まれ、実装は含まれません.接続中にクラスが見つからないエラーが発生します.だから、私たちのテンプレートコードはすべてヘッダファイルに置かなければなりません.管理を容易にするために、ソースファイルに置いて、ヘッダファイルを通じて逆含みを実現することもできます.一部の子供靴は再定義の問題を心配するかもしれませんが、実際にテンプレートをヘッダファイルに置くと、このC++標準では実行され、コンパイラはそれを特殊に処理しているので、プットクラスの定義がヘッダファイルに置かれる心配はありません.
実際にC++の標準ではもう一つのキーワードexportでこの問題を解決することができますが、コンパイラのサポートはまだ少ないので、ここでは話しません.
テンプレートのコンパイルメカニズムはオンデマンドであり、タイプオンデマンドだけでなく、使用されていないメンバー関数に対してもコンパイラはコンパイルしないことが重要です.先日、ネット上でブログをめくったとき、仁兄が話した例に出会った.彼は自分で簡単なテンプレートライブラリを作った.テストの時は問題なかったが、実際の使用過程で大きなトラブルにぶつかった.彼はテスト時に、いくつかのタイプに対してすべてのメンバー関数を使用していないため、これらのメンバー関数コンパイラはそれをコンパイルしていないので、問題は使用期間中に暴露されます.したがって、テンプレートのコンパイルメカニズムを理解することは、今後、独自のテンプレートライブラリを開発する上で重要な役割を果たします.
(ツッコミを入れざるを得ない:これからCSDNの下書き機能を信じないで、昨夜の前夜の編集は全部浪費しました!)
参考文献:
《C++ Primer》 Stanley B.Lippman
Barbara E.Moo
《C++言語プログラム設計》鄭莉董淵何江舟