|

| |
"They
have computers, and they may have other weapons of mass
destruction."
- Janet Reno
"Computer
Science: 1. A study akin to numerology and
astrology, but lacking the precision of
the former and the success of the latter. 2. The
boring art of coping with a large number of
trivialities."
- Stan Kelly-Bootle |
We have observed that if a base class
has a method "Speak()", and that method is overridden in it's derived
class, a
pointer to a base object that is assigned to a derived object
will simply "do the right thing". Example:
//Virtual methods
#include <iostream.h>
//---------------------------------------------------------------------------------------------------------------------
class Monster
{
public:
Monster():itsAge(1) { cout << "Monster
constructor...\n"; }
~Monster() { cout << "Monster
destructor...\n"; }
virtual void
Speak()
const { cout << "It\'s a Monster!\n"; }
protected:
int itsAge;
};
//---------------------------------------------------------------------------------------------------------------------
class Giant: public
Monster
{
public:
Giant() { cout << "Giant
constructor...\n"; }
~Giant() { cout << "Giant
destructor...\n"; }
void Speak()
const { cout << "Fe! Fi! Fo! Fum!\n”; }
};
//---------------------------------------------------------------------------------------------------------------------
int main()
{
Monster *PointerToEvilGiant
=
new Giant;
PointerToEvilGiant->Speak();
return 0;
} |
In the Giant class, what happens if you want to add a method to
Giant that is inappropriate for Monster? Perhaps something like
the method SquashLittlePeopleLikeABug(), which Giants may do, but many other Monsters
may not be capable of. Example:
class Giant:
public Monster
{
public:
Giant() { cout << "Giant constructor...\n"; }
~Giant() { cout << "Giant destructor...\n"; }
void Speak() const { cout << "Fe!
Fi! Fo! Fum!\n”; }
void
SquashLittlePeopleLikeABug() { cout << "Crush!
Kill! Destroy!"; }
};
|
If you called
SquashLittlePeopleLikeABug() on your Monster pointer, you would get a compiler error:
error C2039: 'SquashLittlePeopleLikeABug' : is not a member of 'Monster'
When the compiler tries to resolve
SquashLittlePeopleLikeABug() in its virtual table, there will be no entry. If you have a pointer to a base class assigned to a derived class, it should be because you intend to use that object
polymorphically. Don't try accessing methods specific to a derived class with a base class pointer.
As usual, there is a work around, a method of cheating. You might cast
a base class pointer to a derived type. To do this you need the
dynamic_cast operator.
dynamic_cast operator - The dynamic_cast
operator ensures casting safely, but it is considered by many to be
passé and unpredictable. The purpose of virtual functions is to
allow the virtual table, rather than the programmer, to determine the runtime type of the object.
This helps you quickly find places in your code where you have used this feature and remove them. If you have a pointer to a base class, such as Monster, and you assign to it a pointer to a derived class, such as
Giant, you can use the Monster pointer polymorphically. If you then need to get at the
Giant object to call the SquashLittlePeopleLikeABug() method, you create a
Giant pointer and use the dynamic_cast operator to make the conversion. At runtime the base pointer is examined. If the conversion is proper, the new
Giant pointer is fine, if not, or if you don't have a Giant object, the new pointer will be NULL.
The dynamic_cast is complemented by the
static_cast operator and functions like so:
dynamic_cast<type-id>( expression
) - It converts the operand (expression) to the
object of type <type-id>. The <type-id> must be a
pointer or a reference to a pre-defined class.
static_cast<type-id>( expression
) - It
converts the operand (expression) to the object of type
<type-id> without checking to make sure the conversion is
safe. It is useful for converting a pointer to a base
class object into a pointer to a derived class object.
It can also convert enums to ints and ints to floats.
Example (Note: Depending on the
patch/version, this may not function the same among different compilers)
//Dynamic cast
#include <iostream.h>
//---------------------------------------------------------------------------------------------------------------------
class Mammal
{
public:
Mammal():itsAge(1) { cout << "Mammal constructor...\n"; }
~Mammal() { cout << "Mammal destructor...\n"; }
virtual void Speak() const { cout << "Mammal speak!\n"; }
protected:
int itsAge;
};
//---------------------------------------------------------------------------------------------------------------------
class Cat:
public Mammal
{
public:
Cat() { cout << "Cat constructor...\n"; }
~Cat() { cout << "Cat destructor...\n"; }
void Speak()const { cout << "Meow\n"; }
void Purr()
const { cout << "rrrrrrrrrrr\n"; }
};
//---------------------------------------------------------------------------------------------------------------------
class Dog: public Mammal
{
public:
Dog() { cout << "Dog Constructor...\n"; }
~Dog() { cout << "Dog destructor...\n"; }
void Speak()
const { cout << "Woof!\n"; }
};
//---------------------------------------------------------------------------------------------------------------------
int main()
{
const
int NumberMammals = 3;
int choice;
int i;
Mammal *Zoo[NumberMammals];
Mammal *pMammal;
for(i=0; i<NumberMammals; i++)
{
cout << "(1)Dog (2)Cat: ";
cin >> choice;
if (choice == 1)
pMammal = new Dog;
else
pMammal = new Cat;
Zoo[i] = pMammal;
}
cout << "\n";
for(i=0; i<NumberMammals; i++)
{
Zoo[i]->Speak();
Cat *pRealCat = 0;
pRealCat =
dynamic_cast<Cat*>(Zoo[i]);
//pRealCat
will be NULL if not pointing to a Cat but a Dog
if (pRealCat)
pRealCat->Purr();
else
cout << "Uh oh, not a cat!\n";
delete Zoo[i];
cout << "\n";
}
return 0;
} |
In this example there are two loops. In the
first, the user is asked to add a Cat or Dog object to the array of Mammal pointers in a loop that iterates
three times. This first loop calls the Mammal, Dog and Cat constructors as each object is chosen by the user and entered into an array. The
second loop uses an array to call the appropriate Speak() method from Dog and Cat that overrides the one in Mammal. A pointer to Cat is created, "pRealCat".
It is initialized to 0 and dynamic_cast is used to make sure that the object
the Purr() method is called on is a Cat and not something else for each element of Zoo[i]. If the object is a Cat, the pointer will not be NULL, and will pass the test and cout << "rrrrrrrrr". If the object is not a Cat, the pointer will be NULL, and
so it will fail the test and cout << "Uh oh, not a cat!\n".
Here's another example:
#include <iostream.h>
//---------------------------------------------------------------------------------------------------------------------
class B { };
//---------------------------------------------------------------------------------------------------------------------
class C : public B { };
//---------------------------------------------------------------------------------------------------------------------
class D : public C { };
//---------------------------------------------------------------------------------------------------------------------
void f(D *pd)
{
C *pc = dynamic_cast<C*>(pd); // ok: C is a direct base class
// pc points to C subobject of pd
B *pb = dynamic_cast<B*>(pd); // ok: B is an indirect base class
// pb points to B subobject of pd
}
//---------------------------------------------------------------------------------------------------------------------
void main() {
D DObject;
f(&DObject);
}
|
Example of a hierarchy of classes:
//Shape classes
#include <iostream.h>
enum BOOL { FALSE, TRUE };
//defining our own BOOL, instead of bool
//---------------------------------------------------------------------------------------------------------------------
class Shape
{
public:
Shape(){}
~Shape(){}
//Virtual methods to be overridden in each derived
class
virtual long GetArea() {
return -1; }
//Error,
it's virtual, base class method never meant to be
used
virtual long GetPerim() {
return -1; }
virtual void
Draw() {}
private:
};
//---------------------------------------------------------------------------------------------------------------------
class Circle :
public Shape
{
public:
Circle(int radius):itsRadius(radius){}
~Circle(){}
long GetArea() {
return 3 * itsRadius * itsRadius; }
long GetPerim() {
return 9 * itsRadius; }
void Draw();
private:
int itsRadius;
int itsCircumference;
};
//Draw()
method defined outside class
void Circle::Draw()
{
cout << "Circle drawing routine here!\n";
}
//---------------------------------------------------------------------------------------------------------------------
class Rectangle :
public Shape
{
public:
Rectangle(int len, int width):itsLength(len), itsWidth(width){}
~Rectangle(){}
virtual
long GetArea() {
return itsLength * itsWidth;
}
virtual long GetPerim() {return 2*itsLength + 2*itsWidth; }
virtual int GetLength() {
return itsLength; }
virtual int GetWidth() {
return itsWidth; }
virtual void Draw();
private:
int itsWidth;
int itsLength;
};
//Draw()
method defined outside class
void Rectangle::Draw()
{
for(int i = 0; i<itsLength; i++)
{
for(int j = 0; j<itsWidth; j++)
{
cout << "x "; }
cout << "\n";
}
}
//---------------------------------------------------------------------------------------------------------------------
class Square :
public Rectangle
{
public:
Square(int len);
Square(int len, int width);
~Square(){}
long GetPerim() { return 4 * GetLength(); }
};
//Define
outside of class the overloaded constructor that
takes one parameter
Square::Square(int len):Rectangle(len,len)
{ }
//Define
outside of class the overloaded constructor that
takes two parameters
Square::Square(int len,
int width):Rectangle(len,width)
{
if(GetLength() != GetWidth())
cout << "Error, not a square... a Rectangle??\n";
}
//---------------------------------------------------------------------------------------------------------------------
int main()
{
int choice;
BOOL fQuit = FALSE;
Shape *sp;
while(1)
{
cout << "(1)Circle (2)Rectangle (3)Square (0)Quit: ";
cin >> choice;
switch(choice)
{
case 1: sp = new Circle(5);
break;
case 2: sp = new Rectangle(4,6);
break;
case 3: sp = new Square(5);
break;
default: fQuit = TRUE;
break;
}
if (fQuit)
break;
sp->Draw();
cout << "\n";
} //close while true loop
return 0;
} |
Output:
(1)Circle (2)Rectangle
(3)Square (0)Quit: 1
Circle drawing routine here!
(1)Circle (2)Rectangle (3)Square (0)Quit: 2
x x x x x x
x x x x x x
x x x x x x
x x x x x x |
(1)Circle (2)Rectangle (3)Square (0)Quit: 3
x x x x x
x x x x x
x x x x x
x x x x x
x x x x x
(1)Circle (2)Rectangle (3)Square (0)Quit: 0
|
In this case, the classes Circle, Square and
Rectangle derive from the base class Shape and override its virtual methods.
ADT - Abstract Data Type.
An ADT, or Abstract Data Type, represents an ambiguous or vague concept like Shape, rather than an
object of physical reality like Circle. ADT's are always base classes to other classes. You
can not make an instance of a class that is an ADT. You declare a class to be an ADT by including 1 or more
pure virtual functions in the class declaration.
Pure Virtual Functions - Pure virtual functions are used to
create ADT's. Virtual functions are made pure by initializing them with 0. Example:
virtual void Draw() = 0;
Once a class contains a pure virtual method, we can assume certain
implications:
- The pure virtual
method will NEVER be used, and it must ALWAYS be overridden
in each derived class.
- The class that
contains it is an ADT, and the base class for derived
classes.
- Since the class is an
ADT, there will NEVER be an instance or object of that
class, only of derived classes.
Any class deriving from an ADT inherits the pure virtual function as pure and so must override every pure virtual function
in order to instantiate objects. If Rectangle inherits from Shape, and Shape has 3 pure virtual functions,
then Rectangle must override all 3 of those pure virtual
functions, else it will be an ADT itself. An example of making virtual functions pure:
class Shape { public: Shape(){} ~Shape(){}
//Pure
virtual functions are virtual methods initialized to 0.
This makes the class an ADT.
virtual long GetArea() =
0;
virtual long GetPerim()=
0;
virtual void Draw() =
0;
private: };
Again, let's restate that typically pure virtual functions are never implemented, no objects are created that use them,
and the ADT works purely as the definition of an interface to objects that derive from it.
The ADT never created objects of itself, only its derived classes have
instances. In this way it functions more as an organizational tool
of the "Is-A/Has-A" relationship than an actual, working class
structure. You can provide an implementation to a pure virtual function, so that it can be called by objects derived from the ADT.
You might do this to provide common functionality to all of the overridden functions. Below Shape is an ADT, the Circle class overrides Draw(), and then chains up to the base class function for additional functionality.
Example:
//Implementing pure virtual functions
#include <iostream.h>
enum BOOL { FALSE, TRUE };
//---------------------------------------------------------------------------------------------------------------------
class Shape
{
public:
Shape(){}
~Shape(){}
//Pure
virtual methods - each will HAVE to be overridden in
derived classes. This makes Shape an ADT.
virtual long GetArea() =
0;
//Error
unless overridden in each derived class.
virtual long GetPerim()=
0;
virtual void Draw() =
0;
private:
};
void Shape::Draw()
{
cout << "Abstract drawing mechanism! Never used.\n";
}
//---------------------------------------------------------------------------------------------------------------------
class Circle :
public Shape
{
public:
Circle(int radius):itsRadius(radius){}
~Circle(){}
//We
MUST override the 3 functions below since they were
declared pure in the base class.
long GetArea() {
return 3 * itsRadius * itsRadius; }
long GetPerim() {
return 9 * itsRadius; }
void Draw();
private:
int itsRadius;
int itsCircumference;
};
void Circle::Draw()
{
cout << "Circle drawing routine here!\n";
Shape::Draw();
}
//---------------------------------------------------------------------------------------------------------------------
class Rectangle :
public Shape
{
public:
Rectangle(int len, int width):itsLength(len), itsWidth(width){}
~Rectangle(){}
long GetArea() {
return itsLength * itsWidth; }
long GetPerim() {return 2*itsLength + 2*itsWidth; }
virtual int GetLength() { return itsLength; }
virtual int GetWidth() { return itsWidth; }
void Draw();
private:
int itsWidth;
int itsLength;
};
void Rectangle::Draw()
{
for (int i = 0; i<itsLength; i++)
{
for (int
j = 0; j<itsWidth; j++) {
cout << "x "; }
cout << "\n";
}
Shape::Draw();
}
//---------------------------------------------------------------------------------------------------------------------
class Square :
public Rectangle
{
public:
//2
overloaded constructors
Square(int len);
Square(int len, int width);
~Square(){}
long GetPerim() { return 4 * GetLength(); }
};
Square::Square(int len):Rectangle(len,len)
{ }
Square::Square(int len,
int width):Rectangle(len,width)
{
if(GetLength() != GetWidth())
cout << "Error, not a square... a Rectangle??\n";
}
//---------------------------------------------------------------------------------------------------------------------
int main()
{
int choice;
BOOL fQuit = FALSE;
Shape *sp;
while(1)
{
cout << "(1)Circle (2)Rectangle (3)Square (0)Quit: ";
cin >> choice;
switch(choice)
{
case 1: sp = new Circle(5);
break;
case 2: sp = new Rectangle(4,6);
break;
case 3: sp = new Square(5);
break;
default: fQuit = TRUE;
break;
}
if (fQuit)
break;
sp->Draw();
cout << "\n";
} //close while true loop
return 0;
} |
The output of the example above is
the same as the output of the previous example. However, the
structure is different - the Shape class has now become an ADT. If
only one function is declared pure virtual, the class is an ADT. Circle and Rectangle override Draw(), but chain up to the base method to take advantage of shared functionality. You can derive ADTs from other ADTs. In the code below, no Animal
objects or Mammal objects can be instantiated because they are both base classes and
both are ADTs. However, all Mammals may inherit the Reproduce() method without overriding it.
//Deriving ADTs from other ADTs
#include <iostream.h>
enum COLOR { Red, Green, Blue, Yellow, White, Black, Brown } ;
enum BOOL { FALSE, TRUE };
//---------------------------------------------------------------------------------------------------------------------
class Animal
//ADT. Common base to both horse and
fish.
{
public:
Animal(int);
virtual ~Animal() { cout << "Animal destructor...\n"; }
//Virtual
methods
virtual int GetAge() const { return itsAge; }
virtual void SetAge(int age) { itsAge = age; }
//Pure
virtual methods
virtual void Sleep()
const = 0;
virtual void Eat()
const = 0;
virtual void Reproduce()
const = 0;
virtual void Move()
const = 0;
virtual void Speak()
const = 0;
private:
int itsAge;
};
Animal::Animal(int age):itsAge(age)
{
cout << "Animal constructor...\n";
}
//---------------------------------------------------------------------------------------------------------------------
class Mammal :
public Animal
//ADT.
Multiple inheritance.
{
public:
Mammal(int age):Animal(age)
{ cout << "Mammal constructor...\n"; }
~Mammal() { cout << "Mammal destructor...\n";}
virtual void Reproduce() const
{ cout << "Mammal reproducing!\n"; }
};
//---------------------------------------------------------------------------------------------------------------------
class Fish :
public Animal
{
public:
Fish(int age):Animal(age)
{ cout << "Fish constructor...\n";}
virtual ~Fish() {cout << "Fish destructor...\n"; }
//Virtual
Methods. Fish class MUST override the 5 pure
virtual methods of Animal base class.
virtual void Sleep() const { cout << "fish snoring...\n"; }
virtual void Eat()
const { cout << "fish feeding...\n"; }
virtual void Reproduce()
const { cout << "fish laying eggs...\n"; }
virtual void Move()
const { cout << "fish swimming...\n"; }
virtual void Speak()
const { }
};
//---------------------------------------------------------------------------------------------------------------------
class Horse :
public Mammal
{
public:
Horse(int age, COLOR color ):Mammal(age), itsColor(color)
{ cout << "Horse constructor...\n"; }
virtual ~Horse() { cout << "Horse destructor...\n"; }
virtual void Speak()
const { cout << "Whinny!... \n"; }
virtual COLOR GetItsColor() const { return itsColor; }
virtual void Sleep()
const { cout << "Horse snoring...\n"; }
virtual void Eat()
const { cout << "Horse feeding...\n"; }
virtual void Move()
const { cout << "Horse running...\n";}
protected:
COLOR itsColor;
};
//---------------------------------------------------------------------------------------------------------------------
class Dog :
public Mammal
{
public:
Dog(int age, COLOR color ):
Mammal(age), itsColor(color) { cout << "Dog constructor...\n"; }
virtual ~Dog() { cout << "Dog destructor...\n"; }
virtual void Speak()const { cout << "Whoof!... \n"; }
virtual void Sleep() const { cout << "Dog snoring...\n"; }
virtual void Eat() const { cout << "Dog eating...\n"; }
virtual void Move() const { cout << "Dog running...\n"; }
virtual void Reproduce() const { cout << "Dogs reproducing...\n"; }
protected:
COLOR itsColor;
};
//---------------------------------------------------------------------------------------------------------------------
int main()
{
Animal *pAnimal=0;
int choice;
BOOL fQuit = FALSE;
while(1)
{
cout << "(1)Dog (2)Horse (3)Fish (0)Quit: ";
cin >> choice;
switch (choice)
{
case 1: pAnimal =
new Dog(5,Brown);
break;
case 2: pAnimal =
new Horse(4,Black);
break;
case 3: pAnimal =
new Fish (5);
break;
default: fQuit = TRUE;
break;
}
if(fQuit)
break;
pAnimal->Speak();
pAnimal->Eat();
pAnimal->Reproduce();
pAnimal->Move();
pAnimal->Sleep();
delete pAnimal;
cout << "\n";
}
//close while true loop
return 0;
} |
Output:
(1)Dog (2)Horse (3)Fish
(0)Quit: 1
Animal constructor...
Mammal constructor...
Dog constructor...
Whoof!...
Dog eating...
Dogs reproducing...
Dog running...
Dog snoring...
Dog destructor...
Mammal destructor...
Animal destructor...
(1)Dog (2)Horse (3)Fish (0)Quit: 2
Animal constructor...
Mammal constructor...
Horse constructor...
Whinny!... |
Horse feeding...
Mammal reproducing!
Horse running...
Horse snoring...
Horse destructor...
Mammal destructor...
Animal destructor...
(1)Dog (2)Horse (3)Fish
(0)Quit: 3
Animal constructor...
Fish constructor...
fish feeding...
fish laying eggs...
fish swimming...
fish snoring...
Fish destructor...
Animal destructor...
(1)Dog (2)Horse (3)Fish (0)Quit: 0
|
Here, Mammal overrides the Animal Reproduce()
method to provide a common method of reproduction for all Mammals.
The Fish class must override Reproduce(), since it derives directly from Animal and can not use Mammalian reproduction. Remember:
1. Use ADTs to provide common functionality to related classes. 2. Override all pure virtual functions. 3.
If possible, make pure virtual any function that must be overridden. 4. Don't instantiate an object of an ADT.
It is an abstract, ambiguous base class only.
©2004 C. Germany
|