C++ テンプレート 書いて覚えるための初心者自己中記事
関数テンプレートは扱う型が違うけど処理部分は同じ記述の関数をまとめるためのもの。
// 扱う型は違うが中の処理は同じ記述の関数 int Abs(int a) { return a < 0 ? -a : a; } double Abs(double a) { return a < 0 ? -a : a; }
これまでは関数オーバーロードで引数の型ごとに呼び出していました。
これを関数テンプレートで一つにまとめます。
template <typename TYPE> // template <テンプレート引数> TYPE Abs(TYPE a) { // テンプレート引数を 戻り型 と 引数の型 に使う return a < 0 ? -a : a; }
関数宣言の前に template < テンプレート引数 > を記述して
関数の 戻り型 と 引数の型 にテンプレート引数の変数を使う。
template が窓口になってる感じ?
template <typename TYPE_A, typename TYPE_B> void Show(TYPE_A a, TYPE_B b) { cout << "a: " << a << endl; cout << "b: " << b << endl; } void main() { Show("もじもじ", 3.3); system("pause"); }
関数テンプレート引数は複数でもOK。
関数テンプレートはテンプレート引数部分で型を何でも受け入れてやってくれてるイメージがあったけど、なんと引数の型に合わせた関数を作っているらしい。
普通の関数であればプロトタイプをヘッダファイルに、実装はソースファイルに行ったりできるが、関数テンプレートではヘッダファイルに実装する。
関数テンプレートは型を自動で判断してくれるが、その判断を邪魔してしまうとエラーになる。
template <typename TYPE> void Array(TYPE* array, size_t size, TYPE value) { for (size_t i = 0; i < size; ++i) { array[i] = value; } } template <typename TYPE> void ShowArray(TYPE* array, size_t size) { for (size_t i = 0; i < size; ++i) { cout << array[i] << endl; } } void main() { const int size = 3; int i[size]; double d[size]; Array(i, size, 0); Array(d, size, 0); // double型の配列を第一引数にしているが、第三引数はintっぽいからエラー ShowArray(i, size); ShowArray(d, size); system("pause"); }
この場合は関数テンプレート呼び出しの際に型を明示してあげると理解してくれる。
Array<double>(d, size, 0); // 型を明示させると理解してくれる
また、テンプレート引数が複数ある場合に同じような方法で明示させる場合、例えば引数が2つあったとして
<double , int >
のように2つ書けばよい。
2つ書かなかった場合
< double >
にはテンプレート第一引数だけの明示になる。テンプレート第二引数の型は自動になる。
このままstring もいけました。
Array<string>(str, size, "hoge"); ShowArray(str, size);
関数テンプレートの引数にはクラスもいけます。
さらに、関数テンプレートじゃなくてクラステンプレート。
引数がクラスなのではなくて、クラスがテンプレート。
まず、テキストまま
class.h ↓
template <typename TYPE> class Array { public: Array(int size); Array(const Array& other); void operator=(const Array& other); virtual ~Array(); public: //アクセス関数 TYPE Get(int i) const; void Set(int i, TYPE value); public: //配列のサイズを返す int Size() const; private: //インデックスのチェック void CheckIndex(int i) const; private: TYPE* m_array; //動的配列 int m_size; //配列の要素数 }; template <typename TYPE> Array<TYPE>::Array(int size) { m_array = new TYPE[size]; m_size = size; } template <typename TYPE> Array<TYPE>::Array(const Array& other) { m_size = other.m_size; m_array = new TYPE[m_size]; std::copy(other.m_array, other.m_array + m_size, m_array); } template <typename TYPE> void Array<TYPE>::operator=(const Array& other) { TYPE* array = new TYPE[other.m_size]; delete[] m_array; m_array = array; m_size = other.size; std::copy(other.m_array, other.m_array + m_size, m_array); } template <typename TYPE> Array<TYPE>::~Array() { delete[] m_array; } //アクセス関数(取得) template <typename TYPE> TYPE Array<TYPE>::Get(int i) const { CheckIndex(i); return m_array[i]; } //アクセス関数(設定) template <typename TYPE> void Array<TYPE>::Set(int i, TYPE value) { CheckIndex(i); m_array[i] = value; } //配列のサイズを返す template <typename TYPE> int Array<TYPE>::Size() const { return m_size; } //インデックスのチェック template <typename TYPE> void Array<TYPE>::CheckIndex(int i) const{ if (0 <= i && i <= m_size) { //インデックス正常 } else { std::cerr << "インデックスが不正です" << std::endl << "値 : " << i << std::endl; std::exit(EXIT_FAILURE); } }
Source.cpp ↓
void main() { Array<int> array(5); for (int i = 0, size = array.Size() ; i < size; ++i){ array.Set(i, i * 2); } for (int i = 0, size = array.Size(); i < size; ++i) { cout << array.Get(i) << ' '; } cout << endl; system("pause"); }
長い・・・。
①クラステンプレートの場合、宣言部と実装部分に全部templateの記載が必要なのか。
template <typename TYPE>
すごいめんどくさい。
②メンバ関数の実装部分ではクラス名にテンプレート引数を指定。
void Array<TYPE>::CheckIndex(int i) const{
通常クラス名を書くだけのはずだが、テンプレートの場合はクラス名だけだと存在しないクラスということになっている。
生成されて初めてクラスが存在するのでその生成されたクラスを指定するために<TYPE>が必要になる。
③クラステンプレートでは型を明示させる
void main() {
Array<int> array(5);
明示した宣言をした段階でその型のクラス(TYPE = int)が生成される。
④コンストラクタ名もクラス名なのだけど、そこに<TYPE>は必要ない。
またコピーコンストラクタで引数にクラスを使用しているがそこも必要ない。
引数にクラスの場合は省略可 ということ。
これはその前にすでにクラス名<TYPE> があるから。
ただし、関数の戻り型はそれより前だから省略できない。
次、
デフォルトテンプレート引数。
class.h ↓
template <typename TYPE = char> class Test { public: TYPE Get(); }; template <typename TYPE> TYPE Test<TYPE>::Get() { return 42; }
Source.cpp ↓
void main() { Test<> ch; Test<int> i; cout << ch.Get() << endl; cout << i.Get() << endl; system("pause"); }
テンプレートの引数もデフォルトが設定できる。
Test<> ch;
赤字の部分は省略できないのかな?
うん、ダメみたい。
あと、デフォルトテンプレートには直近の引数を使用できる。
template <typename A , typename B = A > とか
template <typename A , typename B = array<A> > とか
ただし、赤字の部分は半角空白をいれないと >> と勘違いされてしまうので注意。
関数テンプレートの場合、デフォルトテンプレートは使えない。
C++の場合、クラステンプレートの実装をヘッダーファイルではなくソースファイルに記載する方法が一応ある。
方法は実装部分の先頭に export と記載するだけ。
ただし、テキストによるとサポートしているコンパイラが少ないのでやめとけということ。
vector クラステンプレートについて
必要なヘッダファイル vector
arrayの動的配列を便利に扱えるクラステンプレート
void main() { vector<int> v(10); for (size_t i = 0, size = v.size(); i < size; ++i) { v[i] = i + i; } for (size_t i = 0, size = v.size(); i < size; ++i) { cout << v[i] << ' '; } cout << endl; v.resize(5); for (size_t i = 0, size = v.size(); i < size; ++i) { cout << v[i] << ' '; } cout << endl; system("pause"); }
vectorの第二引数について
vector<int> v(10,3); //第一引数が要素数 第二引数が配列に埋めたいもの
第一引数は変更後の要素数
第二引数は、要素数が増えた場合に埋めたいもの
要素数が減った場合はその部分にデストラクタが作用する。
要素数が増えた場合には全要素が別のアドレスへコピーされる。
つまりアドレスが変わる。
vectorクラステンプレートのメンバ関数に慣れるために書いた
void main() { vector<int> v(10); //第一引数が要素数 第二引数が配列に埋めたいもの for (size_t i = 0, size = v.size(); i < size; ++i) { v[i] = i + i; } vector<int>::size_type i = 3; cout << v.operator[](i) << endl; //指定した要素の参照を返す cout << v.at(i) << endl; //同上 ただし指定した要素番号が範囲外の場合 out_of_range例外が投げられる cout << v.front() << endl; //配列の最初の要素の参照を返す cout << v.back() << endl; //配列の最後の要素の参照を返す cout << (int)v.size() << endl; //配列の長さをsize_typeで返す v.resize(10); //配列の要素数を変更する v.reserve(i); //あらかじめ確保されるメモリをsize_typeで指定 cout << (int)v.capacity() << endl; //確保されているメモリの要素数をsize_typeで返す v.clear(); //配列のサイズを0にする cout << v.empty() << endl; //配列のサイズが0ならtrue でなければfalse bool型で返す int x(3); v.push_back(x); //配列の最後に追加する v.pop_back(); //最後の要素を削除する v.begin(); //先頭要素を指すイテレーターを返す(イテレーターってなに?) v.end(); //終端を指すイテレーターを返す(だからイテレーター習ってない) x = 5; v.insert(v.begin(), x); //第一引数(iterator pos)の位置に第二引数を挿入 /* template<typename llterator> void insert(iterator pos, llterator first, lliterator last); //現状理解不能 posの位置に[first, last)の要素を挿入 iterator erase(iterator first , iterator last); //現状理解不能 [first, last)の位置にある要素を削除して後ろの要素を詰める */
ここまで。