ジェネリックプログラミング
これまでの学習を通じて、異なる型の二つの数値の加法を実装するには必然的に関数のオーバーロードを使用しなければならないことを理解しました。
int add_numbers(int x, int y)
{
return x + y;
}
double add_numbers(double x, double y)
{
return x + y;
}
char add_characters(char x, char y)
{
return x + y;
}
しかし、この方法は私たちにとって面倒ではありませんか?実装方法がすべて同じであるならば、一つの型枠(テンプレート)を定義し、使用時に異なる型を入れるだけで異なる型の加法を実現する関数を生成できないでしょうか?その答えは「はい」です。
まず、パラダイムプログラミング、すなわちジェネリックプログラミングを理解する必要があります:
ジェネリックプログラミング:型に依存しない汎用的なコードを作成することで、コードの再利用を実現する手法です。テンプレートはジェネリックプログラミングの基礎となります。
関数テンプレート
概念
関数テンプレートは関数のファミリーを表し、そのテンプレート自体は型に依存しません。使用時にパラメータ化され、実引数の型に基づいて関数の特定の型バージョンが生成されます。
関数テンプレートの形式
template<typename T1, typename T2,......,typename Tn> 戻り値の型 関数名(引数リスト){}
例えば、以下のようなプログラムがあります。
問題点:main関数内の関数呼び出しは同じ関数を呼び出しているのでしょうか?
答えは否定的です。デバッグすると、関数呼び出し時に同じ場所を通過することがわかりますが、これは二つの関数呼び出しが同じ関数を呼び出していることを意味しません。テンプレートから生成された二つの異なる関数が呼び出されています。
逆アセンブルからもこの問題は明らかです。
明らかに、二つの異なる関数が呼び出されています。
関数テンプレートの原理
関数テンプレートは青写真(ブループリント)であり、それ自体は関数ではありません。コンパイラが特定の具体的な型の関数を生成するために使用する型枠です。したがって、テンプレートとは本来私たちが行うべき繰り返し作業をコンパイラに任せるものです。
コンパイラのコンパイル段階では、テンプレート関数の使用に対して、コンパイラは渡された実引数の型に基づいて対応する型の関数を生成して呼び出す必要があります。例えば、double型で関数テンプレートを使用する場合、コンパイラは実引数の型を推論してTをdouble型として決定し、double型を処理するための特化したコードを生成します。文字型についても同様です。
関数テンプレートの具体化
暗黙的具体化
:実引数の型に基づいて自動的にTの型を決定します。
しかし、以下のコードには問題があります。
このコードはコンパイルエラーになります。なぜならx1はint型で、y1はdouble型であり、コンパイラは二つの実引数の型からTが何型であるかを決定できないからです。
上記の問題に対して、以下の二つの解決策があります。
第一の解決策
型強制変換
第二の解決策:明示的な型指定
明示的な型指定
:関数名の後の<>内でテンプレートパラメータの実際の型を指定します。
例えば以下のようになります。
引数の型が一致しない場合は型強制変換が適用されます。
テンプレートパラメータのマッチングルール
1. 非テンプレート関数と同名の関数テンプレートを同時に存在させることができ、その関数テンプレートはその非テンプレート関数として具体化されることもあります。
この場合、add(x1, x1)はテンプレート内で生成された関数ではなく、通常の関数をマッチングします。これは食事に例えると、半成品ではなく完成品を選ぶのと同じです。
2. 非テンプレート関数と同名の関数テンプレートが他の条件すべてで同じ場合、呼び出し時に非テンプレート関数が優先的に呼び出され、テンプレートからインスタンスが生成されることはありません。テンプレートからより適合性の高い関数を生成できる場合、テンプレートが選択されます。
3. テンプレート関数は自動型変換を許可しませんが、通常の関数では自動型変換が可能です。
クラステンプレート
template<class T>
class Stack
{
public:
Stack(size_t capacity = 10)
: _data(new T[capacity])
, _top(0)
, _capacity(capacity)
{}
// デストラクタの使用例:クラス内で宣言、クラス外で定義
~Stack();
T& top()
{
assert(_top > 0);
return _data[_top - 1];
}
void push(const T& value)
{
if (_top >= _capacity)
{
// 容量が不足した場合の拡張処理(省略)
}
_data[_top++] = value;
}
void pop()
{
assert(_top > 0);
_top--;
}
bool empty() const
{
return _top == 0;
}
private:
T* _data;
size_t _top;
size_t _capacity;
};
// 注意:クラステンプレートの関数をクラス外で定義する場合、テンプレートパラメータリストを追加する必要があります
// テンプレートの宣言と定義を.hと.cppファイルに分離することは推奨されません。リンクエラーが発生する可能性があります
template <class T>
Stack<T>::~Stack()
{
if (_data)
delete[] _data;
_top = _capacity = 0;
}
int main()
{
// 具体化
Stack<int> s1;
Stack<char> s2;
return 0;
}