Patterns. External and internal iterator. Implementation in C++

External and internal iterator. Implementation in C++

Before exploring this topic, it is recommended that you familiarize yourself with the following topics:


Contents


Search other websites:




1. Features of the implementation of the Iterator pattern. External and internal iterator

The iterator can be controlled by:

  • a client using this iterator. In this case, the iterator is called external or active. For an external iterator, the client explicitly asks the iterator for the next item to move further through the aggregate. External iterators are more flexible than internal iterators;
  • directly iterator. Such an iterator is called an internal iterator or a passive iterator. In this case, the client does not bother with calling traversal operations, but simply passes some operation to the iterator, which is performed on each element of the aggregate. Internal iterators are easier to use.

 

2. External iterator implementation

 The classic Iterator pattern using an external iterator is shown in Figure 1.

Diagram of the Iterator pattern with a C++ implementation

Figure 1. Diagram of the Iterator pattern with a C++ implementation

You can read more about the implementation of an external iterator in C++ here.

 

3. Implementation of an internal iterator. Example in C++

In order to implement the internal iterator, you need to introduce an additional wrapper class that will implement the external iterator. The following code examines the dynamic array Aggregate, for which an internal iterator is implemented. A simplified version with one container (Aggregate) and one iterator (Iterator) is considered. Polymorphic container and polymorphic iterator are not considered.

The code contains definitions for the following classes:

  • Iterator – an iterator class;
  • Aggregate – an aggregate class within which a dynamic array is implemented;
  • AggregateProcess – a class that provides an implementation of an internal iterator. This is the base class for the MultItemBy2 subclass;
  • MultItemBy2 – a class that overrides the ProcessItem() operation of the base AggregateProcess class. The operation displays the product of the Aggregate container element by 2.

 

#include <iostream>
using namespace std;

// The Iterator pattern for one aggregate and one iterator
// Class preliminary declaration
template <class T>
class Aggregate;

// Iterator class
template <class T>
class Iterator
{
private:
  const Aggregate<T>* aggregate;
  long current;

public:
  // A constructor that receives an aggregated object
  Iterator(const Aggregate<T>* _aggregate) :
    aggregate(_aggregate), current(0)
  { }

  // Move the cursor to the begin
  virtual void First()
  {
    current = 0;
  }

  // Move the cursor to the next item
  virtual void Next()
  {
    current++;
  }

  // Check 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
  {
    return current >= aggregate->Count();
  }

  // Get a list item at the current cursor position
  virtual 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;
    }
  }
};

// Aggregate class.
template <class T>
class Aggregate
{
private:
  // aggregate date (list, array, ...)
  T* data;
  long count;

public:
  // Constructor
  Aggregate(long _count)
  {
    if (_count < 0)
    {
      count = 0;
      data = nullptr;
      return;
    }

    try
    {
      data = new T[_count];
      count = _count;
      for (int i = 0; i < count; i++)
        data[i] = (T)0;
    }
    catch (bad_alloc e)
    {
      cout << e.what() << endl;
      count = 0;
      data = nullptr;
    }
  }

  // Copy constructor
  Aggregate(const Aggregate& obj)
  {
    // Copy data from obj to the current item
    count = obj.count;

    // allocate memory for the array
    try
    {
      data = new T[count];
      for (int i = 0; i < count; i++)
        data[i] = obj.data[i];
    }
    catch (bad_alloc e)
    {
      cout << e.what() << endl;
      count = 0;
    }
  }

  // Method that returns iterator
  Iterator<T>* CreateIterator() const
  {
    return new Iterator<T>(this);
  }

  // Other methods of ConcreteAggregate class
  long Count() const
  {
    return count;
  }

  T GetItem(long index) const
  {
    if ((index >= 0) && (index < count))
      return data[index];
    return data[0];
  }

  // Method that adds an element to the end of the list
  void Append(T value)
  {
    T* data2 = data;
    data = new T[count + 1];

    for (int i = 0; i < count; i++)
      data[i] = data2[i];

    data[count++] = value;
    delete[] data2;
  }

  // Deleting item from the list,
  // index = 0, 1, ..., count-1
  void Remove(long index)
  {
    if ((index >= 0) && (index < count))
    {
      // Delete item from the list
      T* data2 = data;
      data = new T[count - 1];

      for (int i = 0; i < index; i++)
        data[i] = data2[i];

      for (int i = index + 1; i < count; i++)
        data[i - 1] = data2[i];

      count--;
      delete[] data2;
    }
  }

  // Destructor
  ~Aggregate()
  {
    if (count > 0)
      delete[] data;
  }

  // Display the contents of the aggregate
  void Print(string text)
  {
    cout << text << endl;
    for (int i = 0; i < count; i++)
      cout << data[i] << " ";
    cout << endl;
  }
};

// A class that implements an internal iterator.
// This class is the base for subclasses that will implement specific operations
template <class T>
class AggregateProcess
{
private:
  // Iterator in the class
  Iterator<T> it;

public:
  // Constructor - creates an iterator in a class
  AggregateProcess(Aggregate<T>* ag) : it(ag)
  { }

  // Method that processes all elements
  bool ProcessItems()
  {
    bool res = false;

    // 1. Move to the next item in the container
    it.First();

    // 2. Loop through all items
    while (!it.IsDone())
    {
      // 2.1. Process the current item pointed to by the iterator it
      res = ProcessItem(it.CurrentItem());

      // 2.2. If the item has not been processed, then exit the loop
      if (!res) break;

      // 2.3. Move to next item
      it.Next();
    }

    // 3. Retrurn the result
    return res;
  }

protected:
  // A virtual method that processes one item.
  // In subclasses, this method must be replaced with a concrete implementation
  // that does the specific processing.
  virtual bool ProcessItem(const T&) = 0;
};

// A subclass that defines the operation to process an individual element of the container.
// This subclass inherits the AggregateProcess class
// in order to override the ProcessItem() operation.
template <class T>
class MultItemBy2 : public AggregateProcess<T>
{
public:
  // Constructor
  MultItemBy2(Aggregate<T>* ag) : AggregateProcess<T>(ag) { }

protected:
  // Method of handling one element of a container
  bool ProcessItem(const T& item)
  {
    // Print the item multiplied by 2
    cout << item * 2 << " ";

    // Processing was successful
    return true;
  }
};

void main()
{
  // 1. Create an aggregate
  Aggregate<double> ag(0);
  ag.Append(2.55);
  ag.Append(3.8);
  ag.Append(-1.557);
  ag.Append(7.32);

  // 2. Create internal iterator that will display
  //     each element of the container multiplied by 2.
  MultItemBy2<double> it(&ag);
  bool res = it.ProcessItems();
  if (res == true)
    cout << endl << "Ok!" << endl;
  else
    cout << endl << "Error" << endl;
}

 


Related topics