Class hierarchies
class shapelist { \fbox{shapelist}
public:
shapelist(shape* el=0, shapelist* sl=0)
: hd(el), tl(sl) { }
shapelist* insert(shape* el) {
require( el ); el must exist
if (!hd) hd = el;
else return new shapelist(el,this);
}
shape* head() { return hd; }
shapelist* tail() { return tl; }
private:
shape* hd;
shapelist* tl;
}
slide: Base class polymorphism
The polymorphic property of (base class) pointers
allows for the definition of a generic container
for element types ranging over the pointer types
corresponding to the descendants of the base class.
As an example, look at the shapelist
defined in slide [2-base].
The class shapelist declares instance variables
hd (of type ) and tl
(of type ).
The insertion of a new element results
in the creation of a new shapelist which has the
element as a head and the original list as its tail.
Note that the head of each list
is a pointer to an instance of shape
or one of its derived classes.
A pointer is needed since the size of
the actual object will vary depending on the
actual type of the object inserted.
For example, a circle extends a shape
by including an additional instance
variable giving its radius.
Employing base class hierarchies offers
a safe, yet limited, means by which to define
generic (container) types.
(Limited, because the base class imposes
constraints on the actual types for which
the generic data type may be used.)
The $void$ pointer
Employing void pointers allows
for the definition of unconstrained
generic types,
although in a rather crude way.
To understand how that works, it is necessary to reflect on the
meaning of a type system.
One important aspect of a type system
is that it may protect the programmer from a number of common errors,
ranging from trivial typos to inconsistent structures.
In particular, when the type system supports subtyping,
the compiler may check whether the actual relation satisfies
the subtyping relation.
A type gives information concerning the object
to which a variable or expression refers.
The more specific a type, the more a compiler needs to know in order to
assist the programmer in specifying correct programs.
From this perspective, the void pointer figures as the top
of the type hierarchy, since the compiler cannot be assumed
to have any knowledge
concerning its (correct) use.
Consequently, the compiler leaves the responsibility entirely to the user,
who may convert the void pointer to any type at will.
Because of this absence of type information, the void pointer
may indeed be called a generic type.
An example of employing the void pointer to define a generic stack
is given in slide [2-void].
typedef void* type; generic void*
class stack { \fbox{stack}
public:
stack( int n = 12 ) {
top = -1;
impl = new type[n];
}
~stack() { delete[] impl; }
bool empty() { return top == -1; }
void push( type it ) { impl[++top] = it; }
type pop() { return impl[top--]; }
private:
int top; type* impl;
};
slide: Using the void pointer
For storing the contents of the stack, an array of void pointers is created
when evaluating the constructor,
which is deleted when the destructor is called.
Provisions for dynamically enlarging the size of the stack
and for testing its bounds have been omitted, but this is easily
provided for.
An example of using the stack may look as follows:
stack s(100);
char plus = '+'; char c = 'c';
s.push(&plus); s.push(&plus); s.push(&c);
while ( !s.empty() ) {
cout << *(char*) s.pop();
}
To retrieve a value, first the pointer must be cast to a pointer
of the appropriate type, and then it may be de-referenced to deliver the
actual value.
This code clearly illustrates that the user is entirely responsible
for correctly using the stack.
Now when we look at the code,
to push elements on the stack, it is sufficient to take the address of
the value inserted.
However, when removing elements from the stack, the user must know
precisely what the type of the element popped is.
In the example, this first requires the conversion of the void pointer to
a char pointer,
and then a de-reference with an explicit cast to char.
Evidently, generic types of this kind are error-prone,
not to say ugly.