第8章: テンプレートによる汎用プログラミング

これまでの章では、intdouble、あるいは自作の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; }

このように、型ごとに同じロジックの関数をいくつも用意するのは非効率的ですし、バグの温床にもなります。

この問題を解決するのがテンプレートです。テンプレートを使うと、具体的な型を "仮引数" のように扱い、様々な型に対応できる関数やクラスの「設計図」を作ることができます。このような、型に依存しないプログラミングスタイルを**ジェネリックプログラミング(汎用プログラミング)**と呼びます。

関数テンプレート: intでもdoubleでもstringでも動く関数を作る

関数テンプレートを使うと、先ほどのmax関数の問題をエレガントに解決できます。

function_template_intro.cpp

テンプレートの仕組み

template <typename T>という部分が、この関数がテンプレートであることを示しています。

  • template <...>: テンプレートの宣言を開始します。
  • typename T: Tという名前の「型引数」を定義しています。typenameの代わりにclassと書くこともできますが、意味は同じです。Tは、このテンプレートが実際に使われるときに具体的な型(intdoubleなど)に置き換えられます。

main関数でmax_value(10, 20)のように呼び出すと、コンパイラは引数の型がintであることから、Tintだと自動的に判断します(これをテンプレート引数推論と呼びます)。そして、内部的に以下のようなint版の関数を生成してくれるのです。

// コンパイラが内部的に生成するコードのイメージ int max_value(int a, int b) { return (a > b) ? a : b; }

同様に、doublestd::stringで呼び出されれば、それぞれの型に対応したバージョンの関数が自動的に生成されます。これにより、私たちは一つの「設計図」を書くだけで、様々な型に対応できるのです。

クラステンプレート: 様々な型のデータを格納できるクラスを作る

テンプレートの力は、クラスにも適用できます。これにより、様々な型のデータを格納できる汎用的なクラス(コンテナなど)を作成できます。例えば、「2つの値をペアで保持する」クラスを考えてみましょう。

class_template_intro.cpp

クラステンプレートの仕組み

関数テンプレートと基本的な考え方は同じですが、いくつか重要な違いがあります。

  1. 明示的な型指定: 関数テンプレートではコンパイラが型を推論してくれましたが、クラステンプレートの場合は、オブジェクトを生成する際にPair<int, std::string>のように、開発者が明示的に型を指定する必要があります。

  2. インスタンス化: Pair<int, std::string>のように具体的な型を指定してオブジェクトを作ることを、テンプレートのインスタンス化と呼びます。コンパイラは、この指定に基づいてT1intに、T2std::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)**の根幹をなす技術です。次章で学ぶvectormapといった便利なコンテナは、すべてクラステンプレートで実装されています。

この章のまとめ

  • ジェネリックプログラミングは、特定の型に縛られない、汎用的なコードを書くための手法です。
    • テンプレートは、C++でジェネリックプログラミングを実現するための機能です。
    • 関数テンプレートを使うと、様々な型の引数に対して同じ処理を行う関数を定義できます。呼び出し時には、コンパイラがテンプレート引数推論によって型を自動的に決定します。
    • クラステンプレートを使うと、様々な型を扱える汎用的なクラスを定義できます。オブジェクトを生成する際には、< >内に具体的な型を明示的に指定してインスタンス化する必要があります。

テンプレートを使いこなすことで、コードの再利用性が劇的に向上し、より柔軟で堅牢なプログラムを記述できるようになります。

練習問題1: 汎用的なprint関数

任意の型の配列(ここではstd::vectorを使いましょう)を受け取り、その要素をすべて画面に出力する関数テンプレートprint_elementsを作成してください。

practice8_1.cpp

練習問題2: 汎用的なスタッククラス

後入れ先出し(LIFO)のデータ構造であるスタックを、クラステンプレートSimpleStackとして実装してください。以下のメンバ関数を持つようにしてください。

  • void push(T item): スタックに要素を追加する
  • T pop(): スタックの先頭から要素を取り出す
  • bool is_empty(): スタックが空かどうかを返す

std::vectorを内部のデータ格納場所として利用して構いません。int型とchar型で動作を確認してください。

practice8_2.cpp