Iterator Design Pattern
First introduced in CS138, but really learned through CS247.
Resources
- https://refactoring.guru/design-patterns/iterator
- In C++: https://refactoring.guru/design-patterns/iterator/cpp/example
Motivation
We motivated the use of the iterator design pattern in CS247 through the implementation of a linked list. In the example below,
is a not a constant time operation. Takes steps to find the -th node in the list.
- Total time is , so
We canât just give clients access to Nodes again, because we would get Representation Exposure. We want to efficiently looping over a data structure while maintaining Encapsulation.
This is where we can use the Iterator Design Pattern, which provides an abstraction for pointers.
Implementation
Example:
Our clientâs use of an iterator will look like:
These are the functions we need to implement
- for
List::begin/end
functions that return iterators - for
List::Iterator
:!=
to compare++
to go to the next node*
to get the string at current Node.
Implementation
post-fix vs prefix ++
The teacher uses specifically prefix ++ so that the function overloaded is
Iterator& operator++()
. If we wanted it for post-fix ++, we would doIterator& operator++(int i)
(yes disgusting)
If you implement an iterator like this, we can use the range-based for loop syntax:
Const Iterators
If you want to do something like this, it doesnât compile! How should we know that begin/end wonât modify the fields of this constant l
?
Solution: Declare begin/end as const methods, promise we wonât modify fields.
However, we could modify a const List& by using its iterator.
The code below would modify it. (if you want to run the example: go to /u0/s36gong/cs247/1235/examples/lec6/not-const-correct/main.cc
)
This is due to the difference between physical and logical constness.
How to achieve logical constness? begin and end cannot be cast methods, they return Iterators that return modifiable string refs via operator *
Weâll write additional methods cbegin/cend which return const Iterators, which will only provide a cast string&
when calling operator*
.
Can only call cbegin/cend, these donât allow us to modify the list.
Small Complaint: duplication between two iterators. Fix this using a C++ Template.
Old copy-pasta from CS138
Not on exam so i just copy pasted from the slides of Mike Godfrey in CS138.
Problem:
- We have a data container (e.g., stack, linked list, binary search tree, hash table) whose implementation is detailed and hairy, and should be opaque to external clients
- But the client has a need to visit each element in turn to perform some kind of operation on the data, such as:
- Print the elements (âin orderâ)
- Find out how many students were born in 1997 with the last name âMatthewsâ
- Find all 7 digit phone numbers in my contacts, and prepend 519 to them
Letâs assume the desired operation isnât part of the containerâs normal API.
Bad (but do-able) solution:
- Make a friend function/class that is permitted to muck around with the data structure internals for each desired operation
- But this breaks Encapsulation, saddles the (friend) client with learning lots of hairy implementation details, creates risks that they may get things wrong and screw up the internal logic / correctness of the data container
Better solution: Have the data container itself provide an iterator API that allows for abstract navigation through its elements; e.g.,:
- begin() points to first element
- ++ advances to the next element, repeatedly
- end() points after the last element or is ânullâ i.e., its value indicates âweâre done hereâ â Again, note that here, the iterator variable is typically implemented as a pointer, so you have to deference it to access the element
The details of the operations begin(), operator++, and end() are implemented by the container class itself, which already knows the hairy details of the container implementation strategy
- The external client just needs to know how to call the iterator methods, then it can perform the operations on the elements on its own
- Access to the elements by the client is effectively gift-wrapped by the containerâs iterator
The major containers of the C++ Standard Library (vector, list, queue, map, set, etc.) all provide various kinds of iterators for free!