UMBC CMSC202, Computer Science II, Fall 1998,
Sections 0101, 0102, 0103, 0104
19. Virtual Functions & Dynamic Binding
Tuesday November 10, 1998
[Previous Lecture]
[Next Lecture]
Assigned Reading: 9.1-9.4
Handouts (available on-line):
Project 4
Programs from this lecture:
Topics Covered:
- The main topic for this lecture is using virtual functions to achieve
genericness in programs. By "genericness" we mean a class, program, or
function that can work with data of many types. Another name for this is
"polymorphism". In Lecture 10 we used
function pointers to write a Selection Sort function that can sort an array
of any type. Using virtual functions, we can avoid having to deal with
function pointers.
- In our first example, we declare a class GenArray which is intended to
be a base class from which more useful useful array classes will be
derived. (See header file.) Note that
the only data member in GenArray is size and that GenArray does
not have any arrays. This is fine, because the derived classes will have
the "real" arrays as appendant members.
- The GenArray class is an abstract class because it has a
virtual function that is declared as "pure virtual". A pure virtual
function has an = 0 at the end of the declaration.
virtual void print() = 0 ;
This tells the compiler that we will not be supplying the implementation of
the print() function for the GenArray class. This makes sense
because the GenArray class does not have an array, hence there is no need
to print one out. Nevertheless, the derived classes will have an array
member and will have an implementation of the virtual print()
function. The pure virtual declaration allows us to set up the mechanism
for virtual functions without having to write one for the base class. Note
that the compiler will not allow us to define a GenArray object because
GenArray is abstract, but this is exactly what we want.
- The implementation of the
GenArray class is straightforward. We just have to implement the
sort() function. We use Bubble Sort in this example. Note that the
cmp() and swap() functions are virtual. Thus, when
statements in sort() call cmp() it will call the
cmp() member function in the derived class.
- We derive two classes IntArray and FloatArray from GenArray. These
derived classes have, respectively, an array of int and an array
of float as a data member.
-
Our second example of using virtual functions to achieve polymorphism
is the GenList class. This is a linked list class that will allow us
to keep a linked list of objects that are derived from the ListCell
class. (See header file for
ListCell and GenList.) In this example, we will derive an IntCell
class and a StringCell class from ListCell. An IntCell will hold a
single integer and a StringCell a single string. Obviously we can have
more complicated derivations (e.g., you have to do this for
Project 4). After defining these
derivations from ListCell, GenList can be used for a linked list of
integers and a linked list of strings --- and even a linked list which
has some nodes that hold strings and some nodes that hold integers.
- Important points about ListCell and GenList.
- The ListCell destructor must be virtual. Why? When the GenList
destructor is invoked, we want to run down the linked list and free the
memory for each node in the linked list. Some of these nodes might be
IntCells or StringCells. The appropriate IntCell and StringCell destructor
must be called to free these nodes correctly.
- The virtual clone() function in ListCell is used by
append(), prepend() and the GenList assignment operator
to make copies of a node in the linked list. Note that clone()
returns a pointer to ListCell, but might actually point to an IntCell,
StringCell or class derived from ListCell that we haven't even thought
about.
- The virtual cmp() function in ListCell is used by
remove(). Remove must delete every node in the linked list that
matches the given ListCell. This is somewhat tricky. If the linked list
contains both IntCell and StringCell nodes, then we will end up in a
situation where we have to compare an integer to a string. The difficulty
isn't coming up with a reasonable definition of comparing an integer with a
string. The difficulty is being able to detect that is what we are trying
to do. One way to accomplish this is to use the virtual id()
function. All that id() does is return the address of the static
integer variable idvar(). Each derived class will have its own
idvar(). Thus, ListCell::id() will return
&ListCell::idvar, IntCell::id() will return
&IntCell::idvar and StringCell::id() will return
&StringCell::idvar. Since each IntCell::idvar is
static, it is also the case that id() will return the same address
whenever is invoked through an IntCell. The situation is analogous for
StringCell and ListCell. Thus, by comparing the address returned by
id() we can determine if two objects derived from ListCell have
the same type. If they have the same type, then we can compare them in
the usual fashion. If not, we can just make up some consistent answer.
- The cache_status member is used for
Project 4. Whenever the structure
of the linked list is altered (e.g., by a call to chop()),
cache_status is set to dirty.
- In the implementation of StringCell, we need the comparison
operators from the BString class to be constant member functions (i.e.,
does not alter the host object). Unfortunately, we neglected to do this
in the declarations for the second version of the BString class (see
bstring2.h). Thus,
we have to modify the BString class to fix this problem. (See
header file and
implementation of the
third version of the BString class.)
- Discussion on Project 4
[Previous Lecture]
[Next Lecture]
Last Modified:
22 Jul 2024 11:28:49 EDT
by
Richard Chang
Back up
to Fall 1998 CMSC 202 Section Homepage