C++11 以降では、 for (auto x : vector)
といったモダンな形式で for 文を記述することができます。
for 文の右辺には std::vector
などのコレクションクラスのインスタンスを指定します。
これによって、コレクションの先頭から末尾までを、x の参照でループすることができます。
この記事では、自作のコレクションクラスを for 文に利用するためのサンプルを紹介します。
動かしたいコード
自作クラス MyCollection
を定義し、このインスタンスを for ループで利用できるよう拡張します。
次のような main 関数を実行できる状態を目指します。
#include <iostream>
#include <vector>
class MyCollection
{
public:
MyCollection() : v_({3, 1, 4}) {}
private:
std::vector<int> v_;
};
int main()
{
MyCollection collection;
for (auto v : collection)
{
std::cout << v << std::endl;
}
return 0;
}
MyCollection といいつつ、中身は [3, 1, 4] のリストがハードコードされた雑なサンプルです。
MyCollection に begin と end を定義する
前述のコードをそのままビルドすると、当然ながらコンパイルエラーになります。MyCollection
クラスには、for 文でループさせるための準備が何もできていません。
my_collection.cc: In function ‘int main()’:
my_collection.cc:26:19: error: ‘begin’ was not declared in this scope; did you mean ‘std::begin’?
26 | for (auto v : collection)
| ^~~~~~~~~~
| std::begin
In file included from /usr/include/c++/11/string:54,
from /usr/include/c++/11/bits/locale_classes.h:40,
from /usr/include/c++/11/bits/ios_base.h:41,
from /usr/include/c++/11/ios:42,
from /usr/include/c++/11/ostream:38,
from /usr/include/c++/11/iostream:39,
from my_collection.cc:1:
/usr/include/c++/11/bits/range_access.h:108:37: note: ‘std::begin’ declared here
108 | template<typename _Tp> const _Tp* begin(const valarray<_Tp>&) noexcept;
| ^~~~~
my_collection.cc:26:19: error: ‘end’ was not declared in this scope; did you mean ‘std::end’?
26 | for (auto v : collection)
| ^~~~~~~~~~
| std::end
In file included from /usr/include/c++/11/string:54,
from /usr/include/c++/11/bits/locale_classes.h:40,
from /usr/include/c++/11/bits/ios_base.h:41,
from /usr/include/c++/11/ios:42,
from /usr/include/c++/11/ostream:38,
from /usr/include/c++/11/iostream:39,
from my_collection.cc:1:
/usr/include/c++/11/bits/range_access.h:110:37: note: ‘std::end’ declared here
110 | template<typename _Tp> const _Tp* end(const valarray<_Tp>&) noexcept;
| ^~~
エラーの内容を見ると、begin
, end
のメソッドが定義されていないような旨が出力されています。
このエラーを解消するために、MyCollection
に該当メソッドを定義します。begin
, end
メソッド戻り値として Iterator
クラスが必要になるため、MyIterator
クラスも定義しておきます。
MyIterator
にはメンバ n_
を定義し、ここに参照中のインデックスを保持させるようにします。
#include <iostream>
#include <vector>
class MyIterator;
class MyCollection
{
public:
MyCollection() : v_({3, 1, 4}) {}
MyIterator begin();
MyIterator end();
private:
std::vector<int> v_;
};
class MyIterator
{
public:
MyIterator(MyCollection &ref, std::size_t n) : ref_(ref), n_(n) {}
private:
MyCollection &ref_;
std::size_t n_;
};
MyIterator MyCollection::begin() { return MyIterator(*this, 0); }
MyIterator MyCollection::end() { return MyIterator(*this, v_.size()); }
int main()
{
MyCollection collection;
for (auto v : collection)
{
std::cout << v << std::endl;
}
return 0;
}
MyIterator に各種 operator を定義する
もう一度コンパイルすると、また別のエラーが発生します。
今度は MyIterator
クラスに、いくつかの operator
オーバーロードの定義が不足している旨のメッセージです。
my_collection.cc: In function ‘int main()’:
my_collection.cc:33:19: error: no match for ‘operator!=’ (operand types are ‘MyIterator’ and ‘MyIterator’)
33 | for (auto v : collection)
| ^~~~~~~~~~
my_collection.cc:33:19: error: no match for ‘operator++’ (operand type is ‘MyIterator’)
33 | for (auto v : collection)
my_collection.cc:33:19: error: no match for ‘operator*’ (operand type is ‘MyIterator’)
33 | for (auto v : collection)
| ^~~~~~~~~~
これに従って、operator
オーバーロードを3つ定義します。
#include <iostream>
#include <vector>
class MyIterator;
class MyCollection
{
public:
MyCollection() : v_({3, 1, 4}) {}
MyIterator begin();
MyIterator end();
friend MyIterator;
private:
std::vector<int> v_;
};
class MyIterator
{
public:
MyIterator(MyCollection &ref, std::size_t n) : ref_(ref), n_(n) {}
bool operator!=(MyIterator &rhs) { return n_ != rhs.n_; }
MyIterator &operator++()
{
++n_;
return *this;
}
int operator*() { return ref_.v_[n_]; };
private:
MyCollection &ref_;
std::size_t n_;
};
MyIterator MyCollection::begin() { return MyIterator(*this, 0); }
MyIterator MyCollection::end() { return MyIterator(*this, v_.size()); }
int main()
{
MyCollection collection;
for (auto v : collection)
{
std::cout << v << std::endl;
}
return 0;
}
!=
演算子は、for 文の終了条件で用いられます。
end() で生成されるインスタンスクラスとの比較が行われます。
++
演算子は、ループごとのイテレータインクリメントで参照されます。
ここでは前置型(++it の形式)の演算子が必要になります。
*
演算子は、値を参照する際に用いられます。
ここまで定義すると、コンパイルが通って main 関数を実行することができるようになります。
あらかじめハードコードした [3, 1, 4] のリストが順に出力されてプログラムを終えます。
$ g++ my_collection.cc && ./a.out
3
1
4
今回は最小限でプログラムを動作させるために、細部はだいぶ雑な記述となっています。
これをベースにクラスを充実させることで、より実用的なコレクションクラスの定義も可能です。
コメント