Previous Exercise | Next Exercise | Lecture | Top Level

The Foundation Classes

Context

In the monopoly program, we will often deal with collections of similar objects. For this, we will use two classes: Listable and List. Things that go into a list will be derived from Listable, whereas the lists themselves will be derived from List:

list

Let's look at how this works in detail:

List

As seen in the diagram above, a list points to the first Listable in the list. If the list is empty, this pointer contains 0. This pointer should be visible via member functions head() and last(), which return the first and last element of the list. A list is a list of something. We store the information about what kind of object the list contains by keeping a sample of an object in the list. Actually, we will keep a pointer to the sample. We would use the list's constructor do set this pointer. For example, if we wanted to have a list of accounts, we would use the following declaration:

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:

list-syntax

So the procedure for reading the contents of a list is:

  1. Read sample (sample_.read();)
  2. if the reading was ok (sample.read() == ok), add the sample to the list (add(sample_);, otherwise return wrongObject.
  3. Repeat steps 1) and 2).

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_;
};

Listable

The elements of a list are derivatives of the class Listable. As can be seen in the diagram above, a listable should have a pointer to its predecessor and its successor. Since our list will be cyclic, the successor of the last element in the list is the head, and vice versa. In order to detect if we are at the beginning or at the end of the list, we check with the head_ element of the List object. For this, every listable needs a pointer to it's list.

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_;
};

Exercises

Change 3
We need a smarter base class. In order not to waste too much time, we will simply use the final version immediately. Having you code this part would be very time consuming and wouldn't teach you enough. You are not expected to understand everything in this module immediately, but most of it will be explained to you as we go along.

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).

Change 4
This is the big one. This will probably be the most difficult exercise in the whole project. Once you got this to work, it will all be downhill. So gambatte!!!

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.

Note that change Nr. 3 and Nr. 4 require almost no interaction, except that Nr. 3 should be finished before Nr. 4 starts its first build.

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.


Christian Goetze