Let's look at how this works in detail:
List accounts(new Account);
Note how new allocates an Account object on the fly. It's just a matter of good service to have the list's destructor free this sample:
List::~List()
{
free sample_;
}
In addition, we would like to be able to add new elements to a list and clear the list (delete all it's elements). Finally, we have to read and write lists. Using the syntax diagrams we have just learned in the lecture, we define the shape of a list to be:
So the procedure for reading the contents of a list is:
Remember that the wrapper List ... end is being handled by the Base::read() function. When we return wrongObject as we reach the end of the list, the Base::read() function will check for the end keyword, and return ok if it's there (Check the code of the new Base.C that replaces your old version). Writing a List is quite simple. Simply traverse the list, writing out every element. So, here is the header file for our List object:
class List: public Base {
friend class Listable; // Listable needs to be able to modify "head_"
public:
List(Listable* sample = 0);
virtual ~List();
Listable* head();
Listable* last();
void clear();
Listable* add(Listable* element); // adds a _copy_ of "element"
// returns a pointer to that copy.
virtual char* classname();
virtual char* readContents();
virtual char* writeContents();
private:
Listable* sample_;
Listable* head_;
};
Note that from the user's point of view, attempting to get the successor of the last element should return 0. This is why we will use member functions. The implementation of the list will be cyclic, but the user will see that the next element of the last one is 0, and that the element before the first one will also be 0. The checks for this will have to be done in the next() and prev() member functions!
We could have assigned 0 to the pointers instead of using this scheme, but this would have made list insertions and deletions more complex. In my experience, this makes insertions and deletions rather complex, and buggy insertions are very hard to track down. On the other hand, faulty access functions will expose themselves almost instantly.
Note that we wouldn't have this freedom of choice if we didn't use member functions.
Also, we will need to know to which List object we belong to anyway, otherwise we wouldn't be able to delete the head element of a list (why not?).
For deleting an element from a list, we simply use the "delete" operator and let the destructor of Listable do it's job of unlinking itself from the list. When adding an element to a list, we will want to add a copy of that element. It would be natural to use a copy constructor, but unfortunately this will not work. In contrast to the destructor which can be virtual, constructors can only deal with the current class. Therefore, we need to define a virtual copy function duplicate(). This function will need to be implemented by every derivative, since only they will know how to make a proper copy of themselves. You will need to use this function in the insertion functions. Note that we do not need to read and write any data, since the order is already clear from the order in which we read and write the whole list. We therefore do not implement the readContents() and writeContents() member functions. We can also skip the classname() function, since we will never deal directly with a listable, but only with it's derivatives. Listable is therefore another abstract base class. So, this is what the header of Listable looks like:
class Listable: public Base {
public:
friend class List;
// constructors
Listable();
Listable(Listable&);
// destructor
virtual ~Listable(void);
// access
Listable* next(void); // returns 0 if at the end of the list
Listable* prev(void); // returns 0 if at the beginning of the list
List* list(void);
// modification
Listable* insertBefore(Listable& aListable);
Listable* insertAfter(Listable& aListable);
// for lists...
virtual Listable *duplicate(void) = 0;
private:
Listable *next_, *prev_; // implement a _cyclic_ list.
List *list_;
};
This is change Nr. 3. After beginning development (aedb 3; aecd), retrieve files from the baseling with the command aecp Account.C Account.h Base.C Base.h Dice.C Dice.h Game.C Game.h .
Replace Base.h and Base.C with the new versions by clicking on the links here. Note that readContents() and writeContents() now return IOstatus. You will therefore need to change the return type in all other classes from void to IOstatus and make those functions return a value (ok will do for now).
To ensure that the behavior of our new base class is compatible with the old behavior, we run a regression test (aet -reg) in addition to the normal test (aet).
Add the file List.h to your project. This header file contains the definitions of List and Listable. We put both classes into the same file because they are both tightly coupled. It wouldn't make sense to have the one without the other.
Create the file List.C (don't forget to do aenf List.h List.C). Implement the member functions defined in the header file (yeah, right!).
I recommend you look at the code in the test directory. This code will attempt to use your list class. It will test read/write, insertions, deletions and some other common potential bugs.
I recommend that one project member does change Nr. 3, while the others group together and work on Nr. 4. Nr. 3 will probably be done quite fast, so that it will be ready when Nr. 4 will do it's first build. Remember that only one developer may work on one change. The other project members should watch and comment the code. Many pairs of eyes detect errors easier than one lonely programmer. Remember that this is the most complicated piece of code you will need to write in this whole project, and don't give up if you don't get it right immediately.