1. ジェネリックプログラミングとは
異なる型の変数を交換する関数を実装する際、通常は次のように型ごとに関数を用意します:
void swap(int& a, int& b) {
int tmp = a;
a = b;
b = tmp;
}
void swap(double& a, double& b) {
double tmp = a;
a = b;
b = tmp;
}
しかし、このような関数オーバーロードには以下のような問題があります:
- 型が異なるだけでコードの構造が同じため、再利用性が低い
- 新しい型が追加されるたびに新しい関数を実装する必要がある
- 保守性が低く、1つの関数にバグがあると全関数に影響する可能性がある
C++では、このような作業を効率化するための仕組みとしてテンプレートが提供されています。テンプレートは「設計図」のようなもので、異なる型を「材料」として使い、必要なコードを自動生成します。
2. 関数テンプレート
概念
関数テンプレートは、型に依存しない関数の設計図です。呼び出される際に型パラメータを指定することで、その型に応じた関数がコンパイラによって自動生成されます。
基本構文
template<typename T>
戻り値 型関数名(T& a, T& b) {
// 関数の処理
}
例:swap関数のテンプレート化
template<typename T>
void swap(T& a, T& b) {
T tmp = a;
a = b;
b = tmp;
}
この関数は、int、double、charなど、あらゆる型で動作します:
int main() {
int x = 10, y = 20;
swap(x, y); // int型での使用
double a = 3.14, b = 2.71;
swap(a, b); // double型での使用
}
インスタンス化の種類
- 暗黙的インスタンス化: コンパイラが引数の型から自動推論
- 明示的インスタンス化: 呼び出し時に型を明示指定
// 暗黙的インスタンス化
swap(x, y); // Tはintと推論
// 明示的インスタンス化
swap<double>(a, b);
通常関数とテンプレート関数の優先順位
同じ名前の通常関数とテンプレート関数が存在する場合、通常関数が優先的に呼び出されます。ただし、テンプレートがより適切な引数型を持つ場合は、テンプレート関数が選択されます。
int add(int a, int b) {
return a + b;
}
template<typename T>
T add(T a, T b) {
return a + b;
}
int main() {
add(1, 2); // add(int, int)が呼ばれる
add<double>(1.0, 2.0); // add<double>(double, double)が呼ばれる
}
3. クラステンプレート
定義の構文
template<class T>
class クラス名 {
// メンバーの定義
};
例:Stackクラスのテンプレート化
template<class T>
class Stack {
public:
void push(const T& value);
private:
T* data_;
int top_;
int capacity_;
};
template<class T>
void Stack<T>::push(const T& value) {
// メンバー関数の実装
}
クラスのインスタンス化
クラステンプレートは、使用する際に型を指定してインスタンス化する必要があります:
Stack<int> intStack; // int型のスタック
Stack<double> dblStack; // double型のスタック
関数テンプレートとは異なり、クラステンプレートはテンプレート引数を明示的に指定する必要があるため、自動推論は行われません。
typedefとの比較
- typedefは特定の型を別名で定義できるが、複数の型を扱うには不向き
- クラステンプレートは、複数の型に対応した汎用的なクラスを定義できる
- テンプレートの定義と実装を分離する際にはテンプレートパラメータを明示する必要があるため、通常はヘッダファイル内にすべてを記述する