Phase 3
In this step, you will provide iteration for IntList
objects by implementing the following member function(s) of the IntList
class,
begin()
end()
and implement the following member function(s) of the IntList::Iter
class,
operator*()
(the indirection operator)operator++()
(the increment operator)operator==(…)
(the equal-to operator)
These operations are described in the
which also describes the encoding for iterators with a useful diagram. You should have the spec open in another window or tab so you can easily consult it as you work, but for your convenience, the diagram is also reproduced here:
Steps
As you did with the last phase, work through the four steps in order as shown.
And, as always, remember to check out the Helpful Hints!
1. Plan
The encoding for IntList::Iter
is already determined; it is a class that stores a pointer to a Node
(in current_
). What remains is for you to figure out
- What
*iter
does to return the integer stored inside the node. - In the diagram above, the iterator is set to the second node in the list. How can we get to the integer
24
that it contains? - What
++iter
will do to move on to the next node. - In the diagram above, would need update
current_
from its present value,h78
, now point to the next node in list, which ish56
. How do we get that value? - How
iter1 == iter2
will determine whether the two iterators are set to the exact same list node.
Also, in the IntList
class, the begin()
and end()
member functions need to make Iter
objects and return them. You'll need to figure out
- What
myList.begin()
should return. - In the diagram above, it returns an
Iter
withcurrent_
set toh42
. How would we do that? - What
myList.end()
should return. - Note that if you've correctly figured out
++
, you just need to think about what will actually happen if you have an iterator set to the last node in the list (h56
in our diagram) and then call++
on that iterator. Whatever value the iterator is then set to is the past-the-end value.
Note that the Iter
class has two constructors, both of which have been provided. They are
- A
public
default constructor to create an iterator that isn't set at any node in any list.- Any external code can create a default-constructed iterator but they shouldn't use it for anything!
- A
private
(explicit
) parameterized constructor that takes aNode*
and constructs an iterator object, initializingcurrent_
to be the providedNode*
parameter.- We would use this constructor in
begin()
andend()
to make iterators that are set at particular nodes.
- We would use this constructor in
Hay, if the parameterized constructor is
private
, how can we call it frombegin()
? That's inIntList
, not inIntList::Iter
.Good question!
We want the parameterized constructor to be private
because we don't want just anyone to be able to make an Iter
out of a pointer they happen to have. The problem is that we really need code in IntList
to be able to construct Iter
objects using this constructor.
Friendship
Now this is a topic I know a lot about! Let's be friends!!!
Yes, let's! But that's not really what this is about. We have a problem to solve.
A problem that we will solve with... THE POWER OF FRIENDSHIP!!
Sort of...?
In intlist.hpp
, note the line that says
friend class IntList;
This line declares the class IntList
as a friend
to IntList::Iter
. A friend
of IntList::Iter
can access members marked private
in IntList::Iter
. So code within IntList
(e.g. begin()
and end()
) can call the private
constructor and even directly access current_
.
Doesn't
friend
-ship ruin the whole "keep the encoding private" thing?It definitely can! You should use this tool with caution.
But remember that you can only declare a class as a
friend
if you know about it!That does prevent you from accidentally exposing the encoding to arbitrary code.
The most important use is for classes that are designed to work very closely together (a list and its nodes, a collection and its iterator, etc.).
Ideally, as a collective, they will still hide away the implementation details.
Don't Overcomplicate Things!
Although you might have to think and look at the diagram above to figure out what needs to be done, once you've figured it out, it should be very straightforward. If you've done things correctly, there should be no need for loops, or even conditional code. Oftentimes, students wonder “Is this it?? That's all?”
Many of my people wonder the same thing.
Also note that when there is no difference at all in implementation between the const and non-const versions of begin()
and end()
. You'll write the code for the non-const version, and then just paste the exact same code into the const version and it'll work.
Isn't it bad to copy and paste code?
Given how simple these functions should be, it's fine.
Add and Upload Your Image File
For this phase, add your plan as Phase_3.jpg
to the intlist/Planning
folder in your assignment repository (git add
; git commit
; git push
). Make sure the file was added successfully by viewing it on the GitHub website.
2. Write Tests
Next, you need to write some tests.
The user-visible functionality of a list iterator is independent of the specific encoding and implementation choices we've made for our IntList::Iter
class. So in some sense, none of the planning you've done above directly factors into testing, as the only question is “does it work properly as an iterator”.
So, here are some things that should be true of any iterator on any kind of container:
begin()
is equal toend()
if and only if the container is empty.- If you insert some numbers into the container, you should be able to iterate over the container and read back those same numbers.
Maybe you can come up with another couple of ideas.
But you can also think about how someone else might have misunderstood or overcomplicated things when doing the planning stage, and how that issue might show up in an incorrect implementation.
Also, don't forget to add your test functions to
main
so they actually get called!
3. Implement
Use your plan to write the iterator. If your plan is correct, it isn't much code to write.
4. Test & Debug
Test your iterator and debug as needed.
Helpful Hints
Return value of operator++()
In C++, it is legal to run ++(++iter)
to advance an iterator twice. So saying ++iter
doesn't just update iter
to advance it, it also returns a reference to iter
so that more further operations can be performed. In other words, you need to end with
return *this;
so that the member function returns the object it was called on.
Checking for Misuse
Should I check for misuse?
You don't have to. I'm not bothering.
But there are some very simple checks you can add to code that'll catch some kinds of misuse.
But some other kinds of misuse would be very hard to detect.
For example, you probably can catch *
or ++
on end()
or a default constructed iterator.
The explicit
Keyword
Hay! Why is the deal with the
explicit
keyword in the single-parameter constructor forIntList::Iter
?Without it, C++ would be happy to automatically convert
Node*
values intoIntList::Iter
values.Basically, we're turning off some peculiar automatic behavior that you wouldn't have ever expected, doesn't help, and we don't want.
Whenever you write a single-parameter constructor in C++, it is almost always best to declare it explicit
.
(When logged in, completion status appears here.)