今回は簡単な例題として、以前作成したベクタクラス 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;
}