これまでの章では、int
やdouble
、あるいは自作のCar
クラスのように、特定の型に対して処理を行う関数やクラスを作成してきました。しかし、プログラムが複雑になるにつれて、「型は違うけれど、行いたい処理は全く同じ」という状況が頻繁に発生します。例えば、2つの値の大きい方を返すmax
という関数を考えてみましょう。
int max_int(int a, int b) {
return (a > b) ? a : b;
}
double max_double(double a, double b) {
return (a > b) ? a : b;
}
このように、型ごとに同じロジックの関数をいくつも用意するのは非効率的ですし、バグの温床にもなります。
この問題を解決するのがテンプレートです。テンプレートを使うと、具体的な型を "仮引数" のように扱い、様々な型に対応できる関数やクラスの「設計図」を作ることができます。このような、型に依存しないプログラミングスタイルを**ジェネリックプログラミング(汎用プログラミング)**と呼びます。
関数テンプレートを使うと、先ほどのmax
関数の問題をエレガントに解決できます。
template <typename T>
という部分が、この関数がテンプレートであることを示しています。
template <...>
: テンプレートの宣言を開始します。typename T
: T
という名前の「型引数」を定義しています。typename
の代わりにclass
と書くこともできますが、意味は同じです。T
は、このテンプレートが実際に使われるときに具体的な型(int
やdouble
など)に置き換えられます。main
関数でmax_value(10, 20)
のように呼び出すと、コンパイラは引数の型がint
であることから、T
をint
だと自動的に判断します(これをテンプレート引数推論と呼びます)。そして、内部的に以下のようなint
版の関数を生成してくれるのです。
// コンパイラが内部的に生成するコードのイメージ
int max_value(int a, int b) {
return (a > b) ? a : b;
}
同様に、double
やstd::string
で呼び出されれば、それぞれの型に対応したバージョンの関数が自動的に生成されます。これにより、私たちは一つの「設計図」を書くだけで、様々な型に対応できるのです。
テンプレートの力は、クラスにも適用できます。これにより、様々な型のデータを格納できる汎用的なクラス(コンテナなど)を作成できます。例えば、「2つの値をペアで保持する」クラスを考えてみましょう。
関数テンプレートと基本的な考え方は同じですが、いくつか重要な違いがあります。
明示的な型指定:
関数テンプレートではコンパイラが型を推論してくれましたが、クラステンプレートの場合は、オブジェクトを生成する際にPair<int, std::string>
のように、開発者が明示的に型を指定する必要があります。
インスタンス化:
Pair<int, std::string>
のように具体的な型を指定してオブジェクトを作ることを、テンプレートのインスタンス化と呼びます。コンパイラは、この指定に基づいてT1
をint
に、T2
をstd::string
に置き換えた、以下のような新しいクラスを内部的に生成します。
// コンパイラが内部的に生成するクラスのイメージ
class Pair_int_string { // クラス名は実際には異なります
public:
int first;
std::string second;
Pair_int_string(int f, std::string s) : first(f), second(s) {}
void print() {
std::cout << "(" << first << ", " << second << ")" << std::endl;
}
};
Pair<int, std::string>
とPair<std::string, double>
は、コンパイルされると全く別のクラスとして扱われることに注意してください。
クラステンプレートは、C++の強力なライブラリである**STL (Standard Template Library)**の根幹をなす技術です。次章で学ぶvector
やmap
といった便利なコンテナは、すべてクラステンプレートで実装されています。
< >
内に具体的な型を明示的に指定してインスタンス化する必要があります。テンプレートを使いこなすことで、コードの再利用性が劇的に向上し、より柔軟で堅牢なプログラムを記述できるようになります。
任意の型の配列(ここではstd::vector
を使いましょう)を受け取り、その要素をすべて画面に出力する関数テンプレートprint_elements
を作成してください。
後入れ先出し(LIFO)のデータ構造であるスタックを、クラステンプレートSimpleStack
として実装してください。以下のメンバ関数を持つようにしてください。
void push(T item)
: スタックに要素を追加するT pop()
: スタックの先頭から要素を取り出すbool is_empty()
: スタックが空かどうかを返すstd::vector
を内部のデータ格納場所として利用して構いません。int
型とchar
型で動作を確認してください。