C++ テンプレートの使用を開始する
1. 汎用プログラミング
交換関数を実装し、さまざまなタイプのパラメーター実装をサポートしたいとします。次のコードのように、typedefを使用して型の名前を変更できます。
// 将 int 起别名为 DataType
typedef int DataType;
void Swap(DataType& x, DataType& y)
{
DataType tmp = x;
x = y;
y = tmp;
}
int main()
{
DataType x = 0, y = 6;
Swap(x, y);
return 0;
}
このようにして、型を変更する必要があるたびに、int を別の型に変更するだけで済みます。
上記は 1 つの方法ですが、関数のオーバーロードを使用して実装できる別の方法もあります。たとえば、次のとおりです。
void Swap(int& left, int& right)
{
int temp = left;
left = right;
right = temp;
}
void Swap(double& left, double& right)
{
double temp = left;
left = right;
right = temp;
}
void Swap(char& left, char& right)
{
char temp = left;
left = right;
right = temp;
}
上記 2 つの方法はユニバーサル交換機能を実装できますが、次のような欠点があります。
- オーバーロードされた関数は型が異なるだけでコードの再利用率が比較的低く、新しい型が出現する限り、ユーザーは対応する関数を追加するか型を変更する必要があります。
- コードの保守性は比較的低く、1 つのエラーがすべてのオーバーロードでエラーを引き起こす可能性があります。
コンパイラにテンプレートを指示し、コンパイラがそのテンプレートを使用してさまざまなタイプに応じたコードを生成できるようにすることはできますか? 答えは「はい、ここではジェネリック プログラミングを導入する必要があります。ジェネリック プログラミング:型に依存しないユニバーサル コードを記述することは、コードを再利用する手段です。」テンプレートは汎用プログラミングの基礎です。
テンプレートは、関数テンプレートとクラス テンプレートに分かれています。
2. 関数テンプレート
1. 関数テンプレートの概念
関数テンプレートは、型に依存しない関数のファミリーを表し、実際のパラメーターの型に基づいて関数の型固有のバージョンを生成するために使用される場合にパラメーター化されます。
2. 関数テンプレートの形式
関数テンプレートを定義する前に、キーワードtemplateを導入する必要があります。これは、テンプレートを定義するためのキーワードです。
使用形式: template<typename T1, typename T2,......,typename Tn>
、テンプレートキーワードの後のテンプレート パラメーターを山括弧で囲みます。テンプレート パラメーターの数は任意ですが、typename キーワードを使用してテンプレートパラメーターを定義する必要があります。または、classを使用することもできます (注意:クラスの代わりに構造体を使用してください)。
たとえば、交換関数の関数テンプレートは次のとおりです。
template<typename T>
void Swap(T& t1, T& t2)
{
T tmp = t1;
t1 = t2;
t2 = tmp;
}
3. 関数テンプレートの原理
関数テンプレートは設計図であり、関数そのものではなく、コンパイラが特定の種類の関数を生成するために使用する型です。つまり、実際には、テンプレートは、実行する必要がある繰り返しの処理をコンパイラーに渡すことになります。
たとえば、次の図はこのプロセスをよく示しています。
コンパイラーのコンパイル段階では、テンプレート関数を使用するために、コンパイラーは、渡される実際のパラメーターの型に基づいて、呼び出しに対応する型の関数を推測して生成する必要があります。
例: double型の関数テンプレートを使用する場合、コンパイラは実際のパラメータ type の演繹を通じてT をdouble型として決定し、次にdouble型を特別に処理するコードを生成します。同じことが文字型にも当てはまります。つまり、コンパイラはテンプレート インスタンスを使用して、対応する Swap 関数を生成します。
4. 関数テンプレートのインスタンス化
関数テンプレートが異なる型のパラメーターとともに使用される場合、それは関数テンプレートのインスタンス化と呼ばれます。テンプレート パラメーターのインスタンス化は、暗黙的なインスタンス化と明示的なインスタンス化に分けられます。
暗黙的なインスタンス化: 実際のパラメーターに基づいて、コンパイラにテンプレート パラメーターの実際の型を推定させます。たとえば、次のAdd関数テンプレートは 2 つの数値の加算を実装します。
template<class T>
T Add(T a, T b)
{
return a + b;
}
int main()
{
int a = 10, b = 20;
double c = 1.11, d = 2.22;
cout << "sum = " << Add(a, b) << endl;
cout << "sum = " << Add(c, d) << endl;
return 0;
}
上記 2 つの呼び出しのインスタンス化には問題はありませんが、コンパイラは暗黙的なインスタンス化を実行し、実行結果は次のようになります。
しかし、次のように呼び出された場合にコンパイルされますか: Add(a, d)
、答えはノーです。T は実際のパラメータaを通じてintであると推定され、T は実際のパラメータdを通じてdouble型であると推定されますが、テンプレート パラメータ リストにはTが 1 つだけあります。となり、コンパイラはこれを判断できません。最後に、 T がint型またはdouble 型であると判断され、エラーが報告されます。
したがって、現時点では 2 つの解決策があります。
- ユーザー自身がコンバージョンを強制する
- 明示的なインスタンス化を使用する
自分で変換を強制したい場合は、次の方法を使用できます。
int main()
{
int a = 10, b = 20;
double c = 1.11, d = 2.22;
cout << "sum = " << Add((double)a, d) << endl;
cout << "sum = " << Add(a, (int)d) << endl;
return 0;
}
Add関数を呼び出すときに、 aをdoubleに強制したり、dをintに強制したりできます。
明示的なインスタンス化:関数名の後の <> 内にテンプレート パラメーターの実際の型を指定します。
たとえば、上記の問題では、明示的なインスタンス化を使用して問題を解決しています。コードは次のとおりです。
int main()
{
int a = 10, b = 20;
double c = 1.11, d = 2.22;
cout << "sum = " << Add(a, b) << endl;
cout << "sum = " << Add(c, d) << endl;
cout << "sum = " << Add<double>(a, d) << endl;
cout << "sum = " << Add<int>(a, d) << endl;
return 0;
}
関数名の後に山括弧を使用して、テンプレート パラメーターの型を指定します。これが明示的なインスタンス化です。
注: 型が一致しない場合、コンパイラは暗黙的な型変換を実行しようとします。変換が成功しない場合、コンパイラはエラーを報告します。
5. テンプレートパラメータのマッチング原則
非テンプレート関数と同じ名前の関数テンプレートの場合、他の条件が同じ場合、呼び出し時に非テンプレート関数が最初に呼び出され、テンプレートからインスタンスは生成されません。より適切に一致する関数を生成できる場合、テンプレートが選択されます。
たとえば、次の 2 つのコードは次のとおりです。
// 专门处理int的加法函数
int Add(int a, int b)
{
cout << "int Add(int a, int b)" << endl;
return a + b;
}
// 通用加法函数
template<class T>
T Add(T a, T b)
{
cout << "T Add(T a, T b)" << endl;
return a + b;
}
int main()
{
// 与非函数模板类型完全匹配,不需要函数模板实例化
Add(1, 2);
// 模板函数可以生成更加匹配的版本,编译器根据实参生成更加匹配的 Add 函数
Add<int>(1, 2);
return 0;
}
3. クラステンプレート
ユニバーサル スタックを実装する必要があるとします。typedefキーワードを使用して、型のエイリアスを作成できます。型を変更する必要があるときは、常にtypedefを変更するだけで済みます。たとえば、次のStackクラス:
typedef int DataType;
class Stack
{
public:
Stack(size_t capacity = 4)
{
_array = new DataType[capacity];
_capacity = capacity;
_size = 0;
}
~Stack()
{
cout << "~Stack()" << endl;
delete[] _array;
_array = nullptr;
_size = _capacity = 0;
}
private:
// 内置类型
DataType* _array;
int _capacity;
int _size;
};
上記のStackクラスのさまざまな型はtypedef型を変更するだけで済みますが、同時に 2 つのスタックが必要で、1 つのスタックのパラメータがintで、もう 1 つのスタックのパラメータがdoubleである場合、上記のメソッドは機能しません。満足したので、クラス テンプレートを紹介します。
クラス テンプレートの使用方法は次のとおりです。例としてStackクラスを取り上げます。
template<class T>
class Stack
{
public:
Stack(size_t capacity = 4)
{
_array = new T[capacity];
_capacity = capacity;
_size = 0;
}
~Stack()
{
delete[] _array;
_array = nullptr;
_size = _capacity = 0;
}
private:
T* _array;
int _capacity;
int _size;
};
インスタンス化されたオブジェクトは次のとおりです。
int main()
{
Stack<int> st1;
Stack<double> st2;
return 0;
}
Stackはクラス名、Stack<int>
はStack<double>
タイプであることに注意してください。テンプレートのスコープはStackクラスです。
このようにして、 intのパラメータを格納するスタックとdoubleを格納するスタックを 2 つ同時に実装します。