今回は簡単な例題として、以前作成したベクタクラス IntVec をテンプレートを使って書き直してみましょう。名前は Vector とします。なお、C++の標準ライブラリには <vector> や <array> が用意されているので、私たちが Vector クラスを作成する必要はありませんが、テンプレートのお勉強ということで、あえてプログラムを作ってみましょう。
さっそくプログラムを作りましょう。Vector の定義は次のようになります。
リスト : クラス Vector の定義 // 宣言 template<class T> class Vector; template<class T> ostream& operator<<(ostream&, const Vector<T>&); // ベクタクラス template<class T> class Vector { T* buff; size_t buff_size; public: explicit Vector(size_t n) : buff(new T [n]), buff_size(n) { } ~Vector() { delete[] buff; } Vector(const Vector&); // コピーコンストラクタ Vector& operator=(const Vector&); // 代入演算子 Vector(Vector&&); // ムーブコンストラクタ Vector& operator=(Vector&&); // ムーブ代入演算子 T& operator[](int); // 添字演算子 // 出力演算子 << の後ろに <T> が必要 friend std::ostream& operator<< <T> (std::ostream&, const Vector&); // その他のメンバ関数 size_t size() const { return buff_size; } // // ・・・イテレータは省略・・・ // };
Vector で出力演算子を多重定義する場合、関数テンプレートで定義することになります。関数がテンプレートで、それをクラスで friend 宣言する場合、参考 URL: Sun Studio 12: C++ ユーザーズガイド 6.7.3 テンプレート関数のフレンド宣言 によると、あらかじめその関数がテンプレートであることを宣言する必要があるそうです。詳細は参考 URL をお読みください。
宣言の方法は簡単で、プロトタイプの前に template<class T, ...> を付けるだけです。たとえば、template<class T> Vector; とすれば、Vector はテンプレートであることが宣言されます。関数を宣言するときも同じです。 operator<< の前に template <class T> を付けます。引数のデータ型 Vector はテンプレートなので、後ろに <T> を付けます。それから、Vector 内の friend 宣言では、operator<< の後ろに <T> を付けてください。
operator << の定義は次のようになります。
リスト : 出力演算子の多重定義 template<class T> ostream& operator<<(ostream& output, const Vector<T>& v) { output << "["; int i = 0; for (; i < v.size - 1; i++) output << v.buff[i] << ","; output << v.buff[i] << "]"; return output; }
テンプレート仮引数 T を使ってデータ型を記述します。Vector の外側で定義するので、クラス Vector のデータ型は Vector<T> になります。<T> を付け忘れないように注意してください。
次はメンバ関数を作ります。
リスト : メンバ関数 // コピーコンストラクタ template<class T> Vector<T>::Vector(const Vector<T>& v) : buff(new T [v.size]), size(v.size) { for (int i = 0; i < size; i++) buff[i] = v.buff[i]; } // 代入演算子 template<class T> Vector<T>& Vector<T>::operator=(const Vector<T>& v) { if (this != &v) { if (size != v.size) { delete[] buff; size = v.size; buff = new T [size]; } for (int i = 0; i < size; i++) buff[i] = v.buff[i]; } return *this; } // ムーブコンストラクタ template<class T> Vector<T>::Vector(Vector&& v) : buff(v.buff), buff_size(v.buff_size) { v.buff = nullptr; v.buff_size = 0; } // ムーブ代入演算子 template<class T> Vector<T>& Vector<T>::operator=(Vector&& v) { if (this != &v) { delete[] buff; buff = v.buff; buff_size = v.buff_size; v.buff = nullptr; v.buff_size = 0; } return *this; } // 配列添字演算子 template <class T> T& Vector<T>::operator[](int i) { if (i < 0 || i >= size) throw out_of_range("Vector: out of range"); return buff[i]; }
関数定義の先頭に template<class T> を付けて、スコープ解決演算子でクラス名を指定します。このとき、クラス名は Vector<T> になります。あとはとくに難しいところはないでしょう。
次はイテレータを作ります。自作のコンテナクラスにイテレータを定義する場合、STL の <iterator> に用意されているテンプレートクラス iterator を public で継承します。iterator には複数のテンプレート仮引数がありますが、最初の 2 つ (イテレータの種類、データ型) を必ず指定してください。あとの仮引数はデフォルト値が設定されているので、通常はそのままで大丈夫です。
イテレータの種類は次のデータで指定します。
Vector はランダムアクセスができるので、ランダムイテレータを実装します。イテレータは内部クラスで定義すると簡単です。次のリストを見てください。
リスト : イテレータ class Iterator : public iterator<random_access_iterator_tag, T> { Vector* vec; int idx; public: // コンストラクタ Iterator(Vector* v, int n) : vec(v), idx(n) { } // 間接参照 *, -> // ++, -- 演算子 (前置、後置) // +=, -=, +, - 演算子 // 比較演算子 ==, !=, < <=, > >= }; Iterator begin() { return Iterator(this, 0); } Iterator end() { return Iterator(this, buff_size); }
演算子 | 機能 |
---|---|
T& operator*() | 間接参照演算子 |
T* operator->() | 要素がクラス (構造体) の場合、メンバを選択する |
Iterator& operator++() | 前置の ++ 演算子 |
Iterator operator++(int n) | 後置の ++ 演算子はイテレータのコピーを返す (引数はダミー) |
Iterator& operator--() | 前置の -- 演算子 |
Iterator operator--(int n) | 後置の -- 演算子はイテレータのコピーを返す (引数はダミー) |
Iterator& operator+=(size_t n) | イテレータを n 個進める |
Iterator& operator-=(size_t n) | イテレータを n 個戻す |
Iterator operator+(size_t n) | n 個進めた新しいイテレータを返す |
Iterator operator-(size_t n) | n 個戻した新しいイテレータを返す |
bool operator==(const Iterator& iter) const | iter と等しいとき真を返す |
bool operator!=(const Iterator& iter) const | iter と等しくないとき真を返す |
bool operator<(const Iterator& iter) const | iter よりも小さいとき真を返す |
bool operator<=(const Iterator& iter) const | iter 以下のときに真を返す |
bool operator>(const Iterator& iter) const | iter よりも大きいとき真を返す |
bool operator>=(const Iterator& iter) const | iter 以上のとき真を返す |
多重定義する演算子の仕様を上表に示します。プログラムは簡単なので説明は割愛します。詳細はプログラムリストをお読みください。
それは実際に実行してみましょう。次に示す簡単なテストを行ってみました。
リスト : 簡単なテスト int main() { Vector<int> a(8); for (int i = 0; i < a.size(); i++) a[i] = i; Vector<int> b = a; for (int i = 0; i < b.size(); i++) b[i] *= 2; cout << a << endl; cout << b << endl; { Vector<int> c = a; a = b; b = c; } cout << a << endl; cout << b << endl; { Vector<int> c = move(a); a = move(b); b = move(c); } cout << a << endl; cout << b << endl; for (auto iter = a.begin(); iter < a.end(); iter += 2) cout << *iter << " "; cout << endl; auto iter = b.begin(); while (iter != b.end()) cout << *iter++ << " "; cout << endl; for (auto x : a) cout << x << " "; cout << endl; for_each(a.begin(), a.end(), [](int x){ cout << x << " "; }); cout << endl; Vector<pair<string,int>> c(4); c[0].first = "foo"; c[0].second = 1; c[1].first = "bar"; c[1].second = 2; c[2].first = "baz"; c[2].second = 3; c[3].first = "oops"; c[3].second = 4; for (auto iter = c.begin(); iter != c.end(); iter++) cout << iter->first << "," << iter->second << endl; }
$ clang++ vector.cpp $ ./a.out [0,1,2,3,4,5,6,7] [0,2,4,6,8,10,12,14] [0,2,4,6,8,10,12,14] [0,1,2,3,4,5,6,7] [0,1,2,3,4,5,6,7] [0,2,4,6,8,10,12,14] 0 2 4 6 0 2 4 6 8 10 12 14 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 foo,1 bar,2 baz,3 oops,4
コピーコンストラクタ、代入演算子、ムーブコンストラクタ、ムーブ代入演算子は正常に動作しています。添字演算子も大丈夫ですね。STL の仕様に合わせてイテレータを実装すると、範囲 for 文や、for_each() など algorithm の関数も利用することができます。
Vector に pair<string, int> を格納することもできます。このとき、pair のデフォルトコンストラクタによりベクタが初期化されます。ベクタを廃棄するときは、pair のデストラクタが実行されます。-> で pair のメンバ変数 first, second を選択することができます。興味のある方はいろいろ試してみてください。
// // vector.cpp : ベクタクラス (テンプレート版) // // Copyright (C) 2015-2023 Makoto Hiroi // #include <iostream> #include <stdexcept> #include <iterator> #include <algorithm> using namespace std; // 宣言 template<class T> class Vector; template<class T> ostream& operator<<(ostream&, const Vector<T>&); // ベクタクラス template<class T> class Vector { T* buff; size_t buff_size; public: explicit Vector(size_t n) : buff(new T [n]), buff_size(n) { } ~Vector() { delete[] buff; } Vector(const Vector&); // コピーコンストラクタ Vector& operator=(const Vector&); // 代入演算子 Vector(Vector&&); // ムーブコンストラクタ Vector& operator=(Vector&&); // ムーブ代入演算子 T& operator[](int); // 添字演算子 // 出力演算子 << の後ろに <T> が必要 friend std::ostream& operator<< <T> (std::ostream&, const Vector&); // その他のメンバ関数 size_t size() const { return buff_size; } // イテレータ class Iterator : public iterator<random_access_iterator_tag, T> { Vector* vec; int idx; public: // コンストラクタ Iterator(Vector* v, int n) : vec(v), idx(n) { } // 間接参照 T& operator*() { return vec->buff[idx]; } T* operator->() { return &vec->buff[idx]; } // 前置の ++, -- 演算子 Iterator& operator++() { idx++; return *this; } Iterator& operator--() { idx--; return *this; } // 後置の ++, -- 演算子, 引数はダミー Iterator operator++(int n) { Iterator iter(*this); idx++; return iter; } Iterator operator--(int n) { Iterator iter(*this); idx--; return iter; } // +=, -= Iterator& operator+=(size_t n) { idx += n; return *this; } Iterator& operator-=(size_t n) { idx -= n; return *this; } // +, - Iterator operator+(size_t n) { return Iterator(vec, idx + n); } Iterator operator-(size_t n) { return Iterator(vec, idx - n); } // 比較演算子 bool operator==(const Iterator& iter) const { return vec == iter.vec && idx == iter.idx; } bool operator!=(const Iterator& iter) const { return vec != iter.vec || idx != iter.idx; } bool operator<(const Iterator& iter) const { return vec == iter.vec && idx < iter.idx; } bool operator<=(const Iterator& iter) const { return vec == iter.vec && idx <= iter.idx; } bool operator>(const Iterator& iter) const { return vec == iter.vec && idx > iter.idx; } bool operator>=(const Iterator& iter) const { return vec == iter.vec && idx >= iter.idx; } }; Iterator begin() { return Iterator(this, 0); } Iterator end() { return Iterator(this, buff_size); } }; // コピーコンストラクタ template<class T> Vector<T>::Vector(const Vector& v) : buff(new T [v.buff_size]), buff_size(v.buff_size) { for (int i = 0; i < buff_size; i++) buff[i] = v.buff[i]; } // 代入演算子 template<class T> Vector<T>& Vector<T>::operator=(const Vector<T>& v) { if (this != &v) { delete[] buff; buff_size = v.buff_size; buff = new T [buff_size]; for (int i = 0; i < buff_size; i++) buff[i] = v.buff[i]; } return *this; } // ムーブコンストラクタ template<class T> Vector<T>::Vector(Vector&& v) : buff(v.buff), buff_size(v.buff_size) { v.buff = nullptr; v.buff_size = 0; } // ムーブ代入演算子 template<class T> Vector<T>& Vector<T>::operator=(Vector&& v) { if (this != &v) { delete[] buff; buff = v.buff; buff_size = v.buff_size; v.buff = nullptr; v.buff_size = 0; } return *this; } // 添字 template <class T> T& Vector<T>::operator[](int i) { if (i < 0 || i >= buff_size) throw std::out_of_range("Vector: out of range"); return buff[i]; } // 出力 template<class T> std::ostream& operator<<(std::ostream& output, const Vector<T>& v) { output << "["; int i = 0; for (; i < v.buff_size - 1; i++) output << v.buff[i] << ","; output << v.buff[i] << "]"; return output; } // 簡単なテスト int main() { Vector<int> a(8); for (int i = 0; i < a.size(); i++) a[i] = i; Vector<int> b = a; for (int i = 0; i < b.size(); i++) b[i] *= 2; cout << a << endl; cout << b << endl; { Vector<int> c = a; a = b; b = c; } cout << a << endl; cout << b << endl; { Vector<int> c = move(a); a = move(b); b = move(c); } cout << a << endl; cout << b << endl; for (auto iter = a.begin(); iter < a.end(); iter += 2) cout << *iter << " "; cout << endl; auto iter = b.begin(); while (iter != b.end()) cout << *iter++ << " "; cout << endl; for (auto x : a) cout << x << " "; cout << endl; for_each(a.begin(), a.end(), [](int x){ cout << x << " "; }); cout << endl; Vector<pair<string,int>> c(4); c[0].first = "foo"; c[0].second = 1; c[1].first = "bar"; c[1].second = 2; c[2].first = "baz"; c[2].second = 3; c[3].first = "oops"; c[3].second = 4; for (auto iter = c.begin(); iter != c.end(); iter++) cout << iter->first << "," << iter->second << endl; }