Stack Class
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.
Stack template class
(Stack.h)
that is part of the distribution for this lab.
Do not vary from this specification.
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!
Stack:
Bounded_Stack implementation.
Stack to grow until memory is
exhausted. You should use your Lab 4 Unbounded_Stack
implementation.
Stack<T>, not Stack.
And similarly for the Iterator<T> base
class.
delete_elements and copy_elements member
functions implementation-free, where possible.
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).
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.
~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.
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.