Phase 2
In this step, you will plan and implement the following member function(s) of the IntList
class:
push_front(…)
pop_front()
- The destructor
When it comes to testing, you will now also be able to expand test coverage for the
size()
and empty()
functions.
In this phase of development, you will have to do all four steps that we described in detail in the previous phase (i.e., plan, write tests, implement, test).
Steps
We've included some hints for each step.
Be sure to read them before you do each step!
Also, it's very tempting to just skip planning and dive into coding, but drawing a diagram can really help you figure out what the important cases are, and where the data you need is stored (and how to access it).
1. Plan
Plan what the functions listed in the Overview above should do. Upload your planning image as Planning/Phase_2.jpg
.
Upload Your Planning Images with the Right Names and Formats
To get credit for your images, you need to provide your picture files in the right format and with the exact names that we (really the autograder) are expecting, including capitalization, spacing, file extension, and so on. You also need to put the file in the correct directory and git add
, git commit
, git push
to make sure the file(s) end up in your GitHub repo.
Although we haven't tracked down exactly how it occurs, sometimes when students upload images from the command line with git, the images don't get uploaded properly. So it's important that you check your images in the GitHub web interface to make sure they're there and that you can view them. You can also upload images directly using the GitHub web interface rather than using the command line.
2. Write Tests
Add one or more testing functions to intlist-test.cpp
to test all the functions you're writing in this part.
Testing Suggestions
Should I write one test for
push_front()
and another separate test forpop_front()
, or just a single test that checks both?It's up to you.
You can't test all of push_front()
's behavior until pop_front()
is also working, but you can test some of it. Being able to test push_front()
(a bit) before you've even written pop_front()
is (arguably) a good thing.
But push_front()
and pop_front()
clearly have a relationship, so testing them together makes sense.
Would everyone be okay with this sort of idea for a test of both?
- Small loop pushing some
int
s (101, 102, 103, etc.) into the list. On each iteration, checksize()
andempty()
. - Small loop popping all those
int
s off the list. On each iteration, check that we get the right values. - When we're done, be sure that the list is back to being empty.
- Small loop pushing some
Sounds great.
What about MORE loop iterations? How about a million
int
s?It's pretty unlikely that a bigger loop will catch any more bugs than the smaller one.
Don't Test Improper Use
Can I test
pop_front()
on an empty list? My code throws an exception, so I can see if that happens.Noooo!
Users are never supposed to call pop_front()
on an empty list. If they break this rule, they get undefined behavior.
A different team might have put assert(!empty());
at the top of pop_front()
, to check its precondition, and assert
just crashes the program if the assertion fails.
And I don't have any checks in
pop_front()
. Less code! If someone breaks the rules, they get what they get.Exactly. That's totally allowed.
You Can't Explicitly Test the Destructor
If your destructor doesn't work properly, you'll see problems with memory leaks running under Valgrind. But if you've coded it as we suggest, the code should be so simple that it doesn't need much testing anyway.
Double Check Your Tests
When writing tests, make sure you call summarize at the end of the function and return the result!
Also remember to add the appropriate function call(s) to the main
function in intlist-test.cpp
, so your tests actually get run.
3. Implement
Implement the functions listed above. Write them in the order listed.
Notes
new
and delete
A good rule of thumb is “when you write new
, figure out where the delete
should go”. If your code for push_front
creates a new list node with new Node{val, ptr}
, where should you be putting a corresponding delete
to mirror that new
?
All Node
s are on the Heap
Don't create Node
objects on the stack. When you make Node
s, you need to be using new
.
Can I create a
Node
on the stack and then use the address-of operator (&
) to get a pointer to it?What will happen to that object when the function returns?
Uh… It'll go away. And I'll be left with a dangling pointer into the stack. That'd be bad. Actually, I did get some compiler warnings about that.
Don't ignore compiler warnings!
Writing the Destructor
In general, a good way to code is to try to avoid writing “tricky” code. The job of the destructor is to get rid of all the data in the list. You could write an explicit loop that calls delete
, but doing that gives you two places where you have a delete
and only one with a new
, which is a bit asymmetric.
Instead, it's better to just use the code you have. Your job is to throw everything in the list away, and you can just pop things off the list until it's empty. The code for doing that should be very simple and thus obviously correct.
4. Test and Fix
Run all your tests and fix any bugs.
(When logged in, completion status appears here.)