Show Lecture.IteratorInterface as a slide show.
CS253 Iterator Interface
VGA, RJ-45 Ethernet, DisplayPort, USB 2.0 interfaces
Big Picture
- How do you write a container? Something like vector, or list?
- For well-known data structures like an array, linked list, or a tree,
the container itself is fairly easy. The tricky part is the iterator.
- An iterator is required to work with a for loop:
deque con = {11, 22, 33, 44, 55};
for (auto value : con)
cout << value << ' ';
11 22 33 44 55
- The container must have these attributes:
- a type
iterator
, with *
, !=
, and ++
.
- methods
.begin()
and .end()
that return an iterator
- Really, that’s it! The devil is in the details.
- This has nothing to do with templates.
That’s a whole separate aspect.
Why?
- Why does a for loop need
iterator
, .begin()
, and .end()
?
- Because a for loop gets translated, like this:
deque con = {11, 22, 33, 44, 55};
for (auto value : con)
cout << value << ' ';
11 22 33 44 55
deque con = {11, 22, 33, 44, 55};
for (auto it = con.begin(); it != con.end(); ++it) {
auto value = *it;
cout << value << ' ';
}
11 22 33 44 55
- Really, it uses the begin() & end() free functions, which call the
methods, and it saves the end() value in a variable to prevent
computing it multiple times. Details!
Typical structure
class Container {
public:
using value_type = whatever type this contains;
class iterator; // forward declaration
iterator begin() {
return iterator(enough data to indicate start of container);
}
iterator end() {
return iterator(enough data to indicate end of container);
}
};
class Container::iterator {
public:
iterator(…); // establish a location
value_type& operator*(); // get value based on state
iterator& operator++(); // go to the next item
bool operator!=(const iterator &) const; // compare two iterators
private:
declare sufficient data to access elements in the container;
}
Design
- Don’t just start coding like a chicken with its head cut off.
Stop. Plan.
- Know what you’re doing before you do it.
Don’t be an Underwear Gnome.
- It’s all about the nested
class iterator
which encapsulates the
current position within the container.
- The essential questions are:
- What data does the iterator need to establish a position?
- What iterator value indicates
.begin()
?
- What iterator value indicates
.end()
?
- How does the iterator get a value?
- How does the iterator go forward?
Answers, for a string
Since a string s
is essentially a dynamic array of char,
the answers are easy:
What data establishes a position? | char *pointer |
What iterator value indicates .begin() ? | pointer = &s[0] |
What iterator value indicates .end() ? | pointer = &s[s.size()] |
How does the iterator get a value? | *pointer |
How does the iterator go forward? | ++pointer |
This works, but the iterator doesn’t know much about the string, so it
can’t avoid incrementing or decrementing too far.
Different answers, for a string
Instead of a pointer into the string data, let’s point to the string
object itself:
What data establishes a position? | string *con; size_t index; |
What iterator value indicates .begin() ? | con=&s; index=0; |
What iterator value indicates .end() ? | con=&s; index=s.size(); |
How does the iterator get a value? | (*con)[index] |
How does the iterator go forward? | ++index |
How does the iterator get the string size? | con->size() |
Now, the iterator knows the string size, and can do error checking.
The iterator has a pointer to the string object itself, so the
iterator has access to all data & methods of the string.
Comparison
Consider the differences between the two string::iterator
implementations above.
Which is larger?
The second, which has a pointer and an integer.
Do we care?
Probably not. You generally only have a few iterators.
Which can detect incrementing too far?
Only the second. The first has only a pointer. It doesn’t know
where the string ends.
Which can detect indirection on .end()
?
Only the second. The first has only a pointer. It doesn’t know
where the string ends.
Which can skip over certain data?
Only the second. The first wouldn’t know where to stop.
A vector<double> vd
has the same requirements as a string.
What data establishes a position? | vector<double> *con; size_t index; |
What iterator value indicates .begin() ? | con=&vd; index=0; |
What iterator value indicates .end() ? | con=&vd; index=vd.size(); |
How does the iterator get a value? | (*con)[index] |
How does the iterator go forward? | ++index |
How does the iterator get the vector size? | con->size() |
Answers, for a list
A list is a doubly-linked list of nodes, each of which is a struct
containing the data and a pointer to the next struct or nullptr.
What data establishes a position? | Node *pointer |
What iterator value indicates .begin() ? | pointer = the first node in the list, or nullptr |
What iterator value indicates .end() ? | pointer = nullptr |
How does the iterator get a value? | pointer->data |
How does the iterator go forward? | pointer = pointer->next; |
Summary
- Sometimes, all you need is a pointer to the data.
- Sometimes, you need a pointer to the container itself, and an index
into the data.
- For error-checking, a pointer to the data isn’t good enough.
How would you know if you
++
right past the end of the container,
or --
before the start? You need the information in the container
itself to detect that.
- Don’t just copy the container’s data itself into the iterator. The
iterator doesn’t need the actual data, and that would be slow.
Instead, the iterator only needs a pointer to the container—it can get
to everything in the container using that pointer.
It’s not magic
- The nested
iterator
class, even though it’s declared
inside the container class, has no special access to the container
data. I had no access to my parents’ bank accounts.
- In particular, the iterator doesn’t magically know which object it’s
associated with. It needs to have a pointer to the associated
container.
- Sure,
vector<int>::iterator
knows that it’s associated with a
vector<int>
, but which one? There are many vector<int>
objects existing at any moment.
class Outer {
public:
int data;
class iterator {
int get() { return data; } // 🦡 for which Outer object‽
};
};
c.cc:5: error: invalid use of non-static data member ‘main()::Outer::data’