BASIC OOP ~Vol.4~

BASIC OOP ~Vol.4~

Iterator

概要

  • コンテナ※1・複数のオブジェクトを表現する構造
    ・配列、リスト、スタック等が該当する
    の仕様に依存しない反復子の提供

解説

このパターンを用いて新規に実装するケースは多く無いでしょう。
大抵ライブラリ側で用意されているからです。※2・「C++のSTL」や「C#のIEnumrator」等

予め用意されている性質上、多くの方は既に利用した経験があると思いますが
この設計の「存在意義」についてはあまり知られていないように思います。

以下はコンテナの全要素に対して処理を行うコードです。
異なるのはIteratorの使用有無だけで同様の結果になります。

int main()
{
	//	コンテナ型の再定義
	typedef std::vector<int> CONTAINER_TYPE;
	//
	const int cg_numbers[] = { 0, 1, 2, 3, 4, };
	CONTAINER_TYPE numbers(cg_numbers, std::end(cg_numbers));
	//
	{
		//	イテレータ未使用版
		auto size = numbers.size();
		for (auto i = 0; i < size; ++i)
		{
			std::cout << numbers[i] << std::endl;
		}
	}
	{
		//	イテレータ使用版
		for (auto it = numbers.begin();
			it != numbers.end();
			++it)
		{
			std::cout << (*it) << std::endl;
		}
	}
}

実行結果

0
1
2
3
4
0
1
2
3
4

次にコンテナの型を「list」に変更してみます。
Iteratorの使用有無でエラーの有無が分かれるようになりました。

int main()
{
	//	コンテナ型の再定義
	typedef std::list<int> CONTAINER_TYPE;
	//
	const int cg_numbers[] = { 0, 1, 2, 3, 4, };
	CONTAINER_TYPE numbers(cg_numbers, std::end(cg_numbers));
	//
	{
		//	イテレータ未使用版
		auto size = numbers.size();
		for (auto i = 0; i < size; ++i)
		{
			//	※ここでエラー「list」は[]による要素アクセスは不可
			std::cout << numbers[i] << std::endl;
		}
	}
	{
		//	イテレータ使用版
		for (auto it = numbers.begin();
			it != numbers.end();
			++it)
		{
			std::cout << (*it) << std::endl;
		}
	}
}

「list」は要素に対するランダムアクセスを仕様的にサポートしていません。
Iterator未使用版はコードの修正を余儀無くされてしまいます。

利用者に対しコンテナの内部仕様に依存することなく
全要素への参照手段を提供するのがIteratorパターンです。

Iteratorパターンを自作する簡単なサンプルを載せておきますが
使用しているライブラリや開発環境に準拠しますので参考程度に。

//	イテレータスーパークラス
template<typename T_>
class IIterator
{
protected:
	IIterator() = default;
public:
	virtual ~IIterator() = default;
public:
	//	次の要素の有無
	virtual bool hasNext() = 0;
	//	次の要素を返す
	virtual T_ Next() = 0;
};
//	コンテナスーパークラス
template<typename T_>
class IContainer
{
protected:
	IContainer() = default;
public:
	virtual ~IContainer() = default;
public:
	//	イテレータを取得
	virtual IIterator<T_>* Iterator() = 0;
};
//	コンテナサブクラス(ここでは固定の1次元バッファですが好みで自由に)
class CContainer final : public IContainer<int>
{
public:
	enum 
	{ 
		MAX_SIZE = 5,	//	バッファサイズ
	};
public:
	CContainer()
	{
		//	適当な値で初期化…
		for (int i = 0; i < MAX_SIZE; ++i) 
		{ 
			m_aBuffer[i] = i; 
		}
	}
	virtual ~CContainer() = default;
public:
	//	イテレータを取得
	virtual IIterator<int>* Iterator()
	{
		return new CIterator(this);
	}
public:
	//	要素の参照
	int At(int index) const 
	{ 
		return m_aBuffer[index]; 
	}
private:
	int m_aBuffer[MAX_SIZE];	//	固定の要素バッファ
};
//	イテレータサブクラス
class CIterator final : public IIterator<int>
{
	//	CContainerだけが生成できる
	friend class CContainer;
private:
	CIterator(CContainer* pContainer) 
		: IIterator()
		, m_pContainer(pContainer)
		, m_iIndex(0)
	{
	}
	virtual ~CIterator()
	{
		delete this;
	}
public:
	//	次の要素の有無
	virtual bool hasNext()
	{
		return m_iIndex < CContainer::MAX_SIZE;
	}
	//	次の要素を返す
	virtual int Next()
	{
		return m_pContainer->At(m_iIndex++);
	}
private:
	CContainer*	m_pContainer;
	int		m_iIndex;
};
int main()
{
	CContainer c;
	auto it = c.Iterator();
	//
	while (it->hasNext())
	{
		std::cout << it->Next() << std::endl;
	}
}

実行結果

0
1
2
3
4

しっかりしたものを自作する場合はそれなりのコストがかかってしまいますので
このパターンに関しては「存在意義」を覚えるだけで十分だと思います。

脚注

↑ 1. ・複数のオブジェクトを表現する構造
・配列、リスト、スタック等が該当する
↑ 2. ・「C++のSTL」や「C#のIEnumrator」等

Tags: