771-C + + design pattern - iterator pattern

Introduction to iterator pattern

Let's first introduce the meaning of Iterator pattern. The core function of Iterator pattern Iterator is to provide a specific method to access each element in a container sequentially, which will not expose the internal design details of the container (the underlying data structure of the container), but also allow external code to access all elements in the collection transparently.

Firstly, a UML class diagram of iterator pattern is given, and the design is as follows:

Therefore, an iterator should basically provide the following interface operations. The code is as follows:

#include<iostream>

using namespace std;

//Abstract base class for iterators
template<typename _Ty>
class Iterator
{
public:
	//Is there another element
	virtual bool hasNext() = 0;
	//Returns the next element
	virtual _Ty& next() = 0;
};

An abstract base class of iterators is defined above
The hasNext interface indicates whether there is another element inside the aggregate object (i.e. container). The next interface returns the value reference of the next element. Through such an iterator instance, you can traverse and access the internal elements of the container without knowing the specific implementation inside the container. It is completely transparent to users.

Of course, different containers have different internal data structures, so the iterator traverses them in different ways. Therefore, the iterator is generally designed as a nested class of the container and directly implements the logical process of iterator iteration inside the container. Please see the following code example.

Iterator pattern code example

See the following code example:

#include<iostream>

using namespace std;

//Abstract base class for iterators
template<typename _Ty>
class Iterator
{
public:
	//Is there another element
	virtual bool hasNext() = 0;
	//Returns the next element
	virtual _Ty& next() = 0;
};

//Definition of container class
template<typename _Ty>
class CArray
{
public:
	//Only the operation of adding elements to the container is provided
	template<typename _Ty>
	void push(_Ty&& val)
	{
		_vec.push_back(std::forward<_Ty>(val));
	}

	//Implementation of the current container iterator
	class ConcreteIterator : public Iterator<_Ty>
	{
	public:
		//Iterator constructor
		ConcreteIterator(CArray<_Ty>& arr)
			:_array(arr)
		{
			_cur = 0;
		}

		//Determine if there is a next element
		bool hasNext()
		{
			int size = _array._vec.size();
			return _cur < size;
		}

		//Returns the value of the next element
		_Ty& next()
		{
			_Ty& val = _array._vec[_cur];
			_cur++;
			return val;
		}
	private:
		CArray<_Ty>& _array;//Gets a reference to the container that the current iterator
		int _cur;//The element subscript of the current iterator
	};

	//Returns the function interface of the container's iterator
	Iterator<_Ty>* createIterator()
	{
		return new ConcreteIterator(*this);
	}
private:
	vector<_Ty> _vec;//Container for storing elements
};

Here, as the implementation of a container, the bottom layer directly uses the vector container as the storage structure of elements. It internally implements an Iterator, ConcreteIterator, which inherits from the Iterator, rewrites the hasNext and next methods, as a specific Iterator for traversing the CArray container, and provides a createIterator function, The Iterator specially returns the container object. The specific traversal code is as follows:

int main()
{
	//Define container objects
	CArray<int> array;
	//Add right value parameter
	array.push(20);
	array.push(56);
	array.push(3);
	array.push(21);
	//Add lvalue parameter
	int data = 89;
	array.push(89);

	//Iterate through the elements inside the access container object and print
	Iterator<int>* it = array.createIterator();
	while (it->hasNext())
	{
		int val = it->next();
		cout << val << " ";
	}
	cout << endl;

	return 0;
}

In the main function above, the code that the iterator traverses to access the container is general code. No matter how the underlying data structure of the container changes, the way the iterator traverses the container will not change. The underlying difference details are completely encapsulated in the iterator's hasNext and next functions, and the internal operation is completely transparent to the user.

Anyone familiar with the internal iterator design of C++ STL container knows that the design of STL container iterator is essentially the same as the general iterator mode above. There are still some differences in implementation details. Let's make a comparison.

Design of container iterator for C++ STL

The design analogy between the container iterator inside C++ STL and the general iterator pattern above:
1. Iterators are designed as nested types of containers, because different containers need to implement their own iterators for specific data traversal due to their different underlying data structures.
2. The C++STL container uniformly provides the begin() and end() methods to return the iterator of the starting element of the container and the iterator of the subsequent position of the end element, which is similar to the createIterator method provided by the curray container above.
3. The iterator of C++STL judges whether the iteration of the container element is completed by comparing it with the end() of the container, which is similar to the hasNext method of the general iterator above.
4. The iterator of C++STL encapsulates the details of data traversal inside the container in the operator + + operator overload function of the iterator, and provides the operator * () operator overload function of the iterator to access the value of the element traversed by the iterator, which is similar to the function of the next method of the general iterator above.

The following demonstrates the standard use of a C++STL container iterator. The code example is as follows:

#include <iostream>
#include <vector>
#include <ctime>

using namespace std;
int main()
{
	//Define the vector container object and add 20 random integers
	vector<int> vec;
	srand(time(nullptr));
	for (int i = 0; i < 20; ++i)
	{
		vec.push_back(rand() % 100 + 1);
	}

	//All elements of the container are accessed through the iterator of the vector container
	vector<int>::iterator it = vec.begin();
	for (; it != vec.end(); ++it)
	{
		cout << *it << " ";
	}
	cout << endl;

	//C++11 provides a new container traversal syntax foreach, which is implemented by iterators
	for (int val : vec)
	{
		cout << val << " ";
	}
	cout << endl;

	return 0;
}

Tags: C++ Container

Posted on Mon, 08 Nov 2021 05:17:32 -0500 by abhijeet