In this project you will first work through some source code examples provided by the instructor to understand how to work with linked lists with classes. This code will be explained in lecture.
Then, you will adapt your project 2 code to use classes, following the examples provided.
There is nothing to turn in for this step.
Under this directory, there are several sub-directories. Each of which contains a later stage in the development of this code. Try to understand what is happening at each stage. At some stages, the code does not compile. Try to understand why. If the code doesn't compile, a later version will fix the error. Try to understand why the change fixed the problem.
Following the example in the step0/v9 directory, create the following files:
Rewrite your project 2 code to use the object-oriented approach to linked lists (i.e. linked lists embedded in classes) illustrated in step0/v9.
This stage is worth 50 points; five points per menu option that works, as per the table below:
Menu options | comments | points | cumulative |
---|---|---|---|
a(dd), l(ist), q(uit), s(ummarize) | style and correctness | 20 | 20 |
f(ind) | style and correctness | 5 | 25 |
d(elete) | style and correctness | 5 | 30 |
u(pdate) | style and correctness | 5 | 35 |
r(ead from file) | style and correctness s | 5 | 40 |
w(rite to file) | style and correctness | 5 | 45 |
smarter quit | style and correctness | 5 | 50 |
If you are stopping at this stage, turn in a tar file that contains no .o or executable files; only your source code (.h and .cc files), your data file (e.g. xxxx.dat) and a Makefile that includes a make all and make clean rule.
Also, if you are stopping at this stage, create a script p2s2.txt that fully demonstrates your code, and also does a cat of all related source files, data files read, and data files written.
There are still a few key skills you must have to be ready for material in CISC220. That's why I'm basing the final 30% of the third project (3% of your final course grade) on those final concepts. I've put these in what I beleive to be their order of importance to your future success as a C++ programmer (most important to least) so that if you don't finish the entire assignment, you'll maximize the effectiveness of the time you spend.
So, to earn a higher grade for project 3, you need to just add five simple things to your work so far:
For a class with pointers to attributes allocated from the heap, demonstrate that you know how to:
Then, two skills that are important, but don't really depend on whether the class has pointers or not:
If you can do all of these things, plus what you did in step 2, you've got the basics of C++ classes down. There are still many new concepts to learn—such as inheritance, multiple inheritance, virtual functions and polymorphism—but you can learn those as and when you need them. In my experience it is the concepts in the list above that create the MOST trouble for C++ students, not the fancier stuff, and so that is where I'm putting the emphasis.
I'll walk you through it with examples, step by step. In each case, I'm showing you what you would have to do in the context of the Acct_C
and AcctList_C
model classes you saw in steps 0-2, and in some cases with other classes as well. Your job is to understand the examples, and then "do likewise" with your code from the previous steps.
Here's an overview of the rest project. Each of the step names below is a "link" to that step.
Why would you want to do this?
To be sure that when you have attributes that are pointers you get a "deep" copy instead of a "shallow" copy. We've talked in lecture about the problems that can occur with you use "shallow copy" instead of "deep copy", but if you still don't understand, this exercise will help you understand that point better.
Look at the example code in the proj3/step3 directory on the course web site. You'll see two directories, both containing code based on the Acct_C and AcctList_C examples from step 0. Look at the main program, which is the same in both directories. This main program relies on the copy constructor. If the copy constructor doesn't work properly, the program won't produce correct output.
Now compare the two .h files for the AcctList_C class from the two directories. You'll see that in one case, the copy constructor is overloaded, and in the other case it isn't. If you run the main from the directory without the overloaded copy constructor, you'll get wrong output. If you run the main from the directory with the overloaded copy constructor, you'll get good output. It's that simple.
How do you overload the copy constructor?
First, figure out the function prototype you need and add it to the .h file for each of your classes. Here are some examples:
Class | Copy Constructor Prototype |
---|---|
Time_C |
Time_C(const Time_C &orig); |
Acct_C |
Acct_C(const Acct_C &orig); |
AcctList_C |
AcctList_C(const AcctList_C &orig); |
Second, write the body of the function inside the .cc file. The body of the function needs to initalize the data members of the newly constructed object to be a complete copy of the object referred to by the parameter "orig". See the example code for details, as well as examples from your textbook.
How do you demonstrate that it works?
Look at the main program in the example directories acctListNoOverloadCopyConstructor and acctListOverloadCopyConstructor. You'll see that both directories contain exactly the same main program. What this main program does is to create an instance of class AcctList_C called acctList1. It then uses the copy constructor to make a new copy of that object called acctList2. It prints out both of these to show that they are the same. Then it makes a change to each object—adding "Carter" to acctList1
and "Reagan" to acctList2
.
Compile and run both examples. In the example without the copy constructor, we see that the two objects aren't really independent; they are "joined at the heap". In fact, only the pointers were copied, so both acctList1 and acctList2 start out with the same values for head and tail. This means that both Carter and Reagan get added on to the same tail element (i.e. they get added after Ford.) The output that is produced is wrong.
In the example with the copy constructors (note that we have copy constructors for both the Acct_C and AcctList_C classes) the output is correct.
You need to write a separate main program that works in a similar way for your class. This is just a small main program to test that your copy constructor is working. It doesn't have to be a "practical, useful" program like p2s2.cc; its purpose is just to test your new member function.
In the p3s3.cc program, you don't need to read the items from a file; you can add them directly in the main program, as in the example here.
If you this is as far as you get, skip to the "final submission" step for instructions about what to put in your script.
Why would you want to do this?
For the same reason that you overload the copy constructor—just in a different context. If you write:
Time_C t1(13,25); // 1:25pm Time_C t2(t1); // make t2 same as t1 Time_C t3 = t1 // make t3 same as t1
then all three of these invoke the copy constructor. However, suppose you write:
Time_C t1(13,25); // 1:25pm Time_C t2; Time_C t3; t2 = t1; t3 = t1;
In this case, both t2 and t3 are first create with a default constructor, and then initialized using an assignment operator. By default, the assignment operator uses default memberwise copy (i.e. shallow copy). Thus for classes with pointer attributes pointing to storage on the heap, this is no good.
You may wonder why we need a separate overloaded assignment operator, when its job sounds a lot like what a copy constructor does. The answer involves (at least) three subtle points:
x = y;
. the object referred to by x may already have storage on the heap allocated. Before we make a "deep copy" of y and assign x's attributes to point to that copy, we need to recycle any memory already pointed to by x. Otherwise, that storage ends up being "garbage"; stuff allocated on the heap that nothing is pointing to. That results in a memory leak, and eventually your program will crash.x = x;
Yes, I know it is silly to write that, but you don't want your program to crash if you do. Therefore, it is necessary to test for that case.First, figure out the function prototype you need and add it to the .h file for each of your classes. Here are some examples:
Class | Overloaded = Prototype |
---|---|
Time_C |
Time_C & operator =(const Time_C &right); |
Acct_C |
Acct_C & operator = (const Acct_C &right); |
AcctList_C |
AcctList_C & operator = (const AcctList_C &right); |
Then, add that function to your .cc file. Remember to put the class name and the binary scope resolution operator immediately in front of the function name, as shown in the table below:
Class | Overloaded = header in .cc file: |
---|---|
Time_C |
Time_C & Time_C::operator =(const Time_C &right) |
Acct_C |
Acct_C & Acct_C::operator = (const Acct_C &right) { ... |
AcctList_C |
AcctList_C & AcctList_C::operator = (const AcctList_C &right) { ... |
For each of these functions you want to be sure that you do four things. See the example code for step 4 further explanation and example code.
How do you demonstrate that it works?
The programs in the step 4 directory illustrate what happens when overload assignment does and does not work. Look at main3.cc and model your test after that.
If you this is as far as you get, skip to the "final submission" step for instructions about what to put in your script.
Why would you want to do this?
To make sure that you don't run out of space on the heap.
How do you implement the destructor?
First, figure out the function prototype you need and add it to the .h file for each of your classes. Here are some examples:
Class | Destructor Prototype |
---|---|
Time_C |
~Time_C(); |
Acct_C |
~Acct_C(); |
AcctList_C |
~AcctList_C(); |
Next, add the function to your .cc file. Be sure to put the class name and binary scope resolution operator right before the tilde (~) character; for example:
Time_C::~Time_C() { ... }Next, inside the body of the destructor, recycle any memory from the heap by using the
delete
operator. Remember that if the memory was allocated as an array, you need to use delete [] a;
where a
is the array name. See the example code for step 5 for details.
How do you demonstrate that it works?
Any time a constructor is invoked, a matching call to the destructor is automatically inserted at the point where that object goes out of scope. So, you don't have to do anything particularly special to make sure the destructor is getting called.
On the other hand, really showing through testing that a destructor is doing the right thing is difficult. If your program seg faults, its very possible you didn't do it right. If it doesn't, it might mean you did it right, or it might mean you just got lucky. For this assignment, we'll have to rely on the TAs to just look at your code and make sure you did it right.
If you are curious, you can look at the main for this step and turn on debugging (look in the Makefile for the -DDEBUG definition) to see where the destructor is getting called.
Why would you want to do this?
When you have a class Foo_C, you overload the << operator for that class to make output easier. Suppose you have a print() member function that prints out the contents of
an object. If you overload this operator, you can write the much more elegant code on the right, instead of the awkward code on the left:
Before overloading operator<< |
After overloading operator<< |
---|---|
Foo_C x,y; cout << "x= "; x.print(cout); cout << " y= "; y.print(cout); cout << endl; |
Foo_C x,y; cout << "x= " << x << " y= " << y << endl; |
The idea is to make objects of your new class Foo_C "first class citizens" of the language: they can printed out using exactly the same syntax that we use to print out variables of type int
, float
, double
or char.
How is it done?
First determine the correct function prototype for the overloaded stream insertion operator and add it to the .h file for each of your classes. Examples are in the table below. Note that you will have to add #include <iostream>
to your .h file. I've included std::
in front of ostream
in the prototypes below to avoid needing the using namespace std;
statement as well. If you put in the using statement, you could probably leave that off.
Class | Function prototype for << operator |
---|---|
Time_C | std::ostream & operator << (std::ostream & left, const Time_C & right); |
Acct_C | std::ostream & operator << (std::ostream & left, const Acct_C & right); |
AcctList_C | std::ostream & operator << (std::ostream & left, const AcctList_C & right); |
Note that this function prototype must be added outside the class declaration, but still inside the .h file, typically before the #endif wrapper. The reason is that the stream insertion operator is a binary operator, and the left operand is of class ostream; therefore for the stream insertion operator to be a member function, it would have to be added to class ostream, and we don't have any authority to add member function definitions to that class. Instead, we make it a non-member function, and include both the left and right operands in the function prototype as in the examples shown above.
Next, add the function to your .cc file. Here, since it is a non member function, the function header is exactly the same as it appears above. There is NO need for the class name or the binary scope resolution operator; on the contrary, it is a syntax error to include it. This is NOT a member function.
The body of this function is typically very short and sweet; as long as you already implemented a member function with the following prototype:
void print(std::ostream & out ) const;
Then you are all set. Just write the following "all purpose" body for your overloaded operator <<, substituting in the proper class name in place of Foo_C
:
ostream & operator << (ostream & left, const Foo_C & right) { right.print(left); return left; }
If your print function is called something other than print(), or doesn't take an ostream & as a parameter, you might have to make adjustments. See the sample code for step 4 for hints.
Why would you want to do this?
Overloading the + operator for a class allows you to use the + operator on objects of that class, as in the following example. (The full source code for this example can be found in the proj3 subdirectory under step7.)
Time_C start(13,25); // start time is 1:25pm Duration_C howLong(0,50); // class lasts 50 minutes Time_C end = start + howLong; // class ends at 2:15pm cout << "end = "; end.print(); cout << endl;
If you don't overload the + operator, you get a syntax error if you try to use the + operator in this way.
> make main CC -o main main.cc "main.cc", line 13: Error: The operation "Time_C + Duration_C" is illegal. 1 Error(s) detected. *** Error code 1 make: Fatal error: Command failed for target `main' >
However, if you do overload the operator, then this works just fine:
> make CC -c main.cc CC -c time.cc CC -o main main.o time.o > ./main end = 2:15pm >
This make the most sense when the objects you are manipulating represent some kind of number: e.g. amounts of money or time, or mathematical objects that can be added such as sets, complex numbers, and polynomials. However, the plus operator can be overloaded for any class; in one sense, it is just a different syntax for a function call.
For example, instead of the syntax:
acctList.addAccount(x);
we could write instead:
acctList = acctList + x;
Some things to note:
acctList = acctList + x;
also requires that the copy constructor and assignment operator work properly. The copy constructor is used to return the value of the operator+
function, and the operator=
is used to copy the value of the right hand side of the assignment to the left hand side of the assignment. For classes involving pointers to storage on the heap, this means you need to overload those two operators first. If default memberwise copy does the right thing (as with the Time_C and Duration_C classes in the example earlier) then this is a non-issue.acctList += x;instead of the line of code above. You can, but to do so, you must overload the += operator. Overloading + and = separately doesn't give you += for free; you have to do it separately.
acctList = acctList + x;
better than writing acctList.addAccount(x);
in any fundamental way? First, figure out the function prototype you need and add it to the .h file for the class. Here's how:
Realize that when a binary operator is overloaded as a member function, the function prototype has only one argument, which is the right operand of the + sign. That argument is passed as const reference (e.g. const Acct_C & right
). The left operand of the + sign MUST be an object of the same type as the class you are making the member function a part of. It can be accessed via the this
pointer.
So here are some examples:
Code example | Function prototype |
Time_C start(1,25); |
const Time_C operator+(const Duration_C &right); |
AcctList_C acctList1; Acct_C democrat(102, "Clinton"); Acct_C republican(103, "Reagan"); acctList1 = acctList1 + democrat; acctList1 = acctList1 + republican; |
AcctList_C operator+(const Acct_C & right); |
Whether the returned type should be const or non-const, and whether it should be a reference or a value is a matter of debate. For purposes of this project I'll accept anything that works.
The sample code for step 5 shows a few approaches to writing the body of an overloaded + operator; consult these for ideas.
Your final submission should include your .h files and .cc files for your two classes (item class and list class), and a Makefile along with one or more main programs.
You should submit a tar file containing your code, Makefile, and input files. (do a Make clean first; points deducted if you don't)
Also submit a script showing a clean compile (make clean; make all) and demonstration run(s) of all your program(s), along with cat and ls commands where needed to show input and output files.
In addition to your main program for step 2, you should include either
Make it VERY clear from the comments in your program and/or output (cout statements) what steps you are testing.
Your code for the two classes you implemented should still do all the stuff it did for step 2 as well as demonstrate the five new functions you've added (plus any helper functions like print() that you might have added along the way.)