Iterator Design Pattern in Modern C++ is a heavily used pattern i.e. provides facility to traverse data containers sophistically. For simplicity, you can consider a pointer moving across an array, but the real magic comes when you get to the next element of a container, in that case, you need not know anything about how the container is constructed(like sequential(not necessarily be contiguous), associative or hashed). This is handled by the iterator.
By the way, If you haven’t check out my other articles on Behavioural Design Patterns, then here is the list:
- Chain of responsibility
- Command
- Interpreter
- Iterator
- Mediator
- Memento
- Observer
- State
- Strategy
- Template Method
- Visitor
The code snippets you see throughout this series of articles are simplified not sophisticated. So you often see me not using keywords like override
, final
, public
(while inheritance) just to make code compact & consumable(most of the time) in single standard screen size. I also prefer struct
instead of class
just to save line by not writing “public:
” sometimes and also miss virtual destructor, constructor, copy constructor, prefix std::
, deleting dynamic memory, intentionally. I also consider myself a pragmatic person who wants to convey an idea in the simplest way possible rather than the standard way or using Jargons.
Note:
- If you stumbled here directly, then I would suggest you go through What is design pattern? first, even if it is trivial. I believe it will encourage you to explore more on this topic.
- All of this code you encounter in this series of articles are compiled using C++20(though I have used Modern C++ features up to C++17 in most cases). So if you don’t have access to the latest compiler you can use https://wandbox.org/ which has preinstalled boost library as well.
Intent
To facilitate the traversal of data structure.
- Iterator is a core functionality of various containers provided in the standard C++ library. There are lots of cases where you’re using iterators without really knowing what you’re using them. For instance, if you use a range-based for-loop what you’re essentially using is begin, end & operator++ but you don’t see any of it.
- Another example is coroutines that’s also something where you have a method which returns a generator but the generator actually gives you the ability to iterate itself and you don’t see the iterators explicitly in this case either.
Iterator Design Pattern Examples in C++
- A typical example to illustrate iterator is to use single dimensional array & traverse it using pointer(with the same type as the element of the array). But this is a very simple & straight forward scenario where you can not imagine how important iterators are? So we will see the example of basic associative container i.e. Binary Tree.
Binary Tree Iterator
|
|
- The most difficult thing in the above example is implementing
PreOrderIterator::operator++
. So when we’re traversing the tree in pre-order. What’s happening is every time somebody calls plus-plus we need to move to the subsequent elements of the tree and this is a particularly tricky operation. - So as you can see because we don’t have any way of asynchronously yielding the elements. It becomes a really ugly chunk of code. I mean I can go through this particular implementation. But essentially it’s just the preorder traversal as we use to do.
Binary Tree Iterator with C++20 Co-routines
- In a previous example, I’ve deliberately skipped talking about how the traversal was actually implemented. Because if you go up and look at the
PreOrderIterator::operator++
, you can see that it’s a lot of manipulations around thecurrent
pointer & is very ugly. - If you ever came across recursive pre-order traversal, you know that it is very intuitive & concise as follows:
- Now the problem here for not having recursion is that you simply have an operator plus-plus. That gets executed at a time and you need to somehow preserve the state between those consecutive executions.
- But there was no way of suspending execution and then resuming till C++20. Because if you could do that, you could write a proper recursive algorithm that people could actually read. And instead, we have this monstrosity which I’m not going to go through.
- Since C++20, we have a feature in C++ by which we can write above algorithm in an idiomatic way where you can actually read the algorithm and it looks like the algorithm that reflects what you read in Wikipedia or computer science books and the way this is made possible is thanks to C++ coroutines[TODO].
|
|
- If you are unable to understand
pre_order_impl
, I would suggest you go through this talk. After thatpre_order_impl
would be self explainable. - Moreover, I have compiled above snipped using cppcoro library with clang 9.0.0 on wandbox.
Boost Iterator Facade Design Pattern in C++
- If you have gone through my Facade Design Pattern article, you know that the first word in the above title i.e. Facade pronounces as `fa;sa;d`.
- Boost Iterator Facade is quite simply a very useful base class that you can add to an iterator very quickly and intuitively i.e. define the operations which make up that iterator. And to explain that I have taken a simple singly-linked list example as below:
|
|
- Some quick things to note here:
- Inheritance mention the type of traversal by
boost::forward_traversal_tag
. - We have some override methods like
increment()
,equal()
,dereference()
, etc.
- Inheritance mention the type of traversal by
You can read more about it in boost Iterator Facade Documentation.
Benefits of Iterator Design Pattern
- Maintains good cohesion which means code is easier to understand, use & test since the iterator uses the Single Responsibility Principle and Open-Closed Principle.
- Loose coupling between data structures & algorithm as an algorithm does not have to know the way of traversal & even underlying data structure in some cases.
- You can extend the Iterators to traverse collection & collection of the collections.
- You can combine the Visitor & Iterator Design Pattern to traverse & execute some operation over the collection of different types.
Summary by FAQs
What is the purpose of Iterator Design Pattern?
To abstract away the underlying structure in which the data are kept for traversal & operations.
How do I use Iterator for traversing the collection of the collections?