CS 342 Lab 5: Virtual Stack Class

 

http://www.cs.wustl.edu/~levine/courses/cs342/labs/lab5/

Lab date: 22 February - 2 March 2000
Due date: 12 March 2000

Objective:

Given two different implementations of the Stack class, a useful next step is to combine them virtually so that clients can choose whether they want a bounded or unbounded stack, and yet treat instances polymorphically. Using inheritance and dynamic binding, build and test both bounded and unbounded implementations of the ADT Stack. Both versions will inherit from the same abstract base class.

Note that most of the methods in this class are pure virtual functions, which means they must be provided by a subclass. In addition, there is no private: section in this class because there are no private implementation details in the Stack abstract base class.

Preparation:

  1. Review the header file for the Stack template class (Stack.h) that is part of the distribution for this lab. Do not vary from this specification.

  2. Review the patterns used in this lab, including

  3. As a starting point, use your implementations for the bounded stack and unbounded stack. You can run make to automatically generate the Bounded_Stack and Unbounded_Stack files from your Labs 3 and 4. However, be sure to check that the generation was successful.

    Note that the virtual stack ADT has its own specification and contains some slight variations from previous assignments, although you should be able to draw from those assignments in completing this one.

    Please note the some of the member functions must be implemented in the Stack<T> base class only. For example, if you attempt to implement equality or inequality operators in your derived classes, you'll probably get compilation warnings about hiding the functions in the base class. So, be sure to remove the functions that aren't needed from your Bounded_Stack and Unbounded_Stack implementations.

    You should be able to reuse your Array and Node classes without modification.

    Please note that you'll have to provide destructor definitions for both the Stack and Iterator template classes. That's because destructors are called all the way up the inheritance hierarchy. It doesn't matter that they're pure virtual in the base class, they still get called!

Assignment:

  1. Given a specification for an abstract Stack class, implement two versions that derive from Stack:

    1. Bounded Stack - The first version uses an array whose bounds are fixed at creation time. You should use your Lab 3 Bounded_Stack implementation.

    2. Unbounded Stack - The second version uses a linked list, which allows a Stack to grow until memory is exhausted. You should use your Lab 4 Unbounded_Stack implementation.

    Be sure to carefully specify the base template class in your code, to avoid obscure compiler errors. For example, the base class is Stack<T>, not Stack. And similarly for the Iterator<T> base class.

  2. Implement iterators for each type of stack to help in making your delete_elements and copy_elements member functions implementation-free, where possible.

  3. Several stack methods (such as operator=, operator==, operator!= and top) must be implemented in the base class using the Template Method pattern. For example, here is an implementation of operator=:

    
    // Assignment operator (performs assignment)
    template <class T>
    Stack<T> &
    Stack<T>::operator= (const Stack<T> &s)
    {
      // These functions use iterators to perform class-specific operations.
      this->delete_elements ();
      this->copy_elements (s);
    
      return *this;
    }
    

    (Hint: iterators are very helpful for implementing the other template methods)

NOTE You must define the read method of each derived class so that it replaces the contents of the stack with the values read in.

NOTE You must define the derived class print and read methods so that for a given parameterized type T, each derived class read method is a dual of every derived class print method. That is (for a given parameterized type T), if a derived stack class instance's print method is called with a given iostream, and then the read method of any other derived stack class instance that can hold values of the same type T (no matter what those current values are, and no matter the stack's concrete class) is called immediately after with the same iostream, then at that point the two stacks must be equivalent. How you do this is up to you, but one suggested approach is to print/read the number of elements first, then the value of each element (hint: the order in which you print and read the values can make the read method in particular either easier or harder to implement for certain derived classes).

Iterators:

The key to making your subclass implementations of Stack work effectively is to define an Iterator base class. Iterator is a pattern that allows clients to access elements in an aggregate collection sequentially without revealing the collection's implementation details. To apply this pattern to the current assignment, you will need to subclass the base class Iterator to define classes that iterate over Bounded_Stack and Unbounded_Stack instances transparently.


template <class T>
class Bounded_Stack_Iterator : public Iterator<T>
{
  // ...
};

template <class T>
class Unbounded_Stack_Iterator : public Iterator<T>
{
  // ...
};
Naturally, the Factory Methods for the iterator methods on Bounded_Stack and Unbounded_Stack will return the appropriate concrete Iterator. Note that the Iterator abstract base class has the same functionality, but is somewhat different from that of the examples seen in class.

In order for your Iterators to access the internals of their corresponding structures, they can be declared friends. By way of example, in the Bounded_Stack class declaration, this statement declares its iterator as a friend:


friend class Bounded_Stack_Iterator<T>;

You can put your Iterator class declarations and implementations in dedicated header (.h) and implementation (.cc) files, or put them into the corresponding Stack files. With separate files, you have to be very careful with #include directives, to avoid problems with circularity. It is easiest to put the Bounded_Stack_Iterator in Bounded_Stack.h and put the Unbounded_Stack_Iterator in Unbounded_Stack.h. Because the Iterator classes are so closely related to their corresponding Stack classes, this is further a reasonable partitioning of classes into files.

The main program included in the lab is similar to the earlier test programs, and it includes a Stack instantiation with a non-integral type. You can modify this code as necessary to adequately test your implementation. Note the inclusion of a separate Factory that creates or destroys an appropriate Stack subclass upon request.

Obtaining the Lab 5 distribution:

At a Unix shell prompt, type ~cs342/bin/lab5. That will copy several files to a new lab5 subdirectory. You can cd lab5 and enter make to compile the code, if you like. However, it will not build until your implementation is filled in.

NOTE: you can use the distribution mechanism to revert one or more of your files at any time. First, delete the file (or better, mv it to a file with a different name, such as Stack.cc.BAD). Then, run cvs update to update your workspace. That will replace any missing files. It will also let you know of any files that you modified but did not delete. cvs is in pkg gnu, in case you don't find it at first.

After obtaining the Lab 5 distribution, run make to copy your Array, Node, and History header and implementation files from Labs 3 and 4. The first time you run make, it will generate the bounded and unbounded Stack header and implementation files. It uses the versions that are in your Lab 3 and Lab 4 directories.

What to Submit:

By the due date (Sunday 12 March 1159 pm), submit all source files from your final solution. Include a laboratory writeup file (named readme) documenting what you did to satisfy this assignment. readme contains a minimum list of sections that you must provide. (Please replace the comments in [] with your descriptions.) These files will be submitted automatically when you execute the command:
make turnin
NOTE: there is a Makefile target that allows you to test what you are going to turn in:
make test_turnin

It places the output that will be turned in into the TEST_TURNIN directory. Please use make test_turnin, and verify that the files that you will submit are correct.

IMPORTANT: Please try to finish your lab during the 2 week period before Spring Break. We have extended the deadline for Lab 5 from the 5th to the 12th to minimize conflicts with travel plans, etc. before the break. However, lab, e-mail and newsgroup support over the break, including on the due date, will be only minimally available if at all. Please plan accordingly.


Please see the Lab 4 assignment for notes on using the History class to help track and count instantiations and deletions of an instrumented class.


Please see the Lab 2 assignment for hints on compiling templates.


CS 342 home page