The Iterator pattern. Features of implementation in C++ for a polymorphic container and a polymorphic iterator
This topic is a continuation of the topic:
Contents
- 1. The structure of the Iterator pattern in C++ codes. Picture
- 2. Components of the Iterator pattern. A minimal set of classes to implement a C++ pattern that supports multiple containers and multiple iterators
- 3. One or more specific containers
- 4. One or more specific iterators
- Related topics
Search other websites:
1. The structure of the Iterator pattern in C++ codes. Picture
Figure 1 shows the structure of the Iterator pattern for a certain container class (list, dynamic array, queue, etc.). The figure reflects the structure for any number of containers (polymorphic container) and any number of iterators (polymorphic iterator). Of all the possible structures, this structure is the most versatile.
For educational purposes, a fragment of its implementation in C++ is added to each block of the structure.
Figure 1. Diagram of the Iterator pattern with a C++ implementation
⇑
2. Components of the Iterator pattern. A minimal set of classes to implement a C++ pattern that supports multiple containers and multiple iterators
The block diagram of the Iterator pattern, which provides support for multiple containers and multiple iterators, is shown in Figure 4. For this implementation of the pattern, the following classes need to be developed.
2.1. Abstract class Aggregate
The abstract class Aggregate defines the interface of the container. Typically, this class contains declarations of a pure virtual CreateIterator() method to return an iterator, which is the abstract class Iterator.
// Abstract class of aggregate (interface) template <class T> class Aggregate { public: // Method that will return an iterator for some list virtual Iterator<T>* CreateIterator() const = 0; };
⇑
2.2. The abstract class Iterator
The abstract class Iterator defines an iterator interface. Here you can specify the basic bypass methods according to the following example
// Abstract class of iterator template <class T> class Iterator { public: // Move to the beginning of the list virtual void First() = 0; // Move to the next item in the list virtual void Next() = 0; // Checking if the end of the list has been reached, // the current cursor position is behind the last item in the list virtual bool IsDone() const = 0; // Get a list item at the current cursor position virtual T CurrentItem() const = 0; };
When using an iterator, the concept of a cursor or the current position of the element in question arises.
If desired, the programmer can declare additional workarounds. For example, the End() method – moves the cursor to the last element of the list.
⇑
3. One or more specific containers
Any container is a class that implements one of the aggregated data structures (list, dynamic array, queue, etc.). In our case (Figure 2), the container name is generic and defined as ConcreteAggregate. In our case (Figure 2), the container name is generic and defined as ConcreteAggregate. However, in real programs, the name can be changed to something more readable (List, Array, Vector, Map, Queue, etc.).
The ConcreteAggregate class implements the Aggregate interface class. This means that the ConcreteAggregate class implements the CreateIterator() method.
The container class code could be something like this
// Concrete Aggregate - Implements the Aggregate interface template <class T> class ConcreteAggregate : public Aggregate<T> { private: // aggregate data (list, array, ...) // ... public: Iterator<T>* CreateIterator() const; // Other methods of ConcreteAggregate class // ... };
For example, for a dynamic array, the abbreviated version of the C++ code might be
// Concrete dynamic array template <class T> class Array : public Aggregate<T> { private: // Internal variable T* array; // data in the form of dynamic array long count; // number of items public: // Constructor Array(long size); // Copy constructor Array(const Array& obj); // Returns the number of items in the collection long Count() const; // Get an element of an array at a given index, // if the index is incorrectly specified, the function returns nullptr T& Get(long index) const { ... } // Implementation of the method that creates an iterator Iterator<T>* CreateIterator() const { ... } // Implementing a method that adds an item to an array void Append(T value) { ... } // Deleting item from array, index = 0, 1, ..., count-1 void Remove(long index) { ... } // Destructor ~Array() { ... } // Other components of the class // ... };
⇑
4. One or more specific iterators
For any aggregated object (container), various strategies for traversing its elements can be implemented. A concrete iterator implements one of these strategies
// Concrete iterator template <class T> class ConcreteIterator : public Iterator<T> { private: const ConcreteAggregate<T>* aggregate; long current; public: // Constructor that gets the aggregated object ConcreteIterator(const ConcreteAggregate<T>* _aggregate) : aggregate(_aggregate), current(0) { } // To the first item void First() { current = 0; } // To the next item void Next() { current++; } // Check whether the end of the list bool IsDone() const { return current >= aggregate->Count(); } // Outputs the current item T CurrentItem() const { if (!IsDone()) return aggregate->GetItem(current); else { // Here is an error, you can throw an exception or perform other actions cout << "Error." << endl; return 0; } } };
Following the above example, you can create your own iterator that will traverse the elements of the container in some specific way.
⇑
Related topics
- General information. Methods of implementation. Structural scheme. Example on C++
- External and internal iterator. Implementation in C++
- The Iterator pattern. Implementation in C#
- The Iterator pattern. Java implementation with support for polymorphic container and polymorphic iterator
⇑