|

Inheritance hierarchies
reflect real world relationships. To understand our world, we
break it up into bits and group
these bits together into classifications.
"Great
spirits have always found violent opposition from
mediocrities. The latter cannot understand it when a
man does not thoughtlessly submit to hereditary
prejudices but honestly and courageously uses his
intelligence."
- Albert Einstein
"He
who asks is a fool for five minutes, but he who does
not ask remains a fool forever."
- Chinese Proverb |
I. The "Has-a" Relationship
If something breathes, moves, eats and reproduces it is an Animal
object. We group together those things that possess the
characteristics of Animal objects, demonstrating the "has-a"
relationship: an Animal eats, an Animal reproduces, and an Animal
is usually capable of some form of locomotion. There are other
objects in the universe that are not Animal: plants which
photosynthesize and rocks which do not consume food. For an object
to be an Animal, it must possess distinctive characteristics relative to
its "has-a" relationship. There are a set of physical
characteristics as well a a set of actions that correspond to Animal.
If we were going to create an Animal class, its data members and methods
would be defined by the "has-a" relationship. Example:
class Animal {
public:
Animal(){ }
~Animal() { }
//Methods - actions of an animal
void Eat() {
cout << "Eating."; }
void
Reproduce() { cout << "Reproducing."; }
void Move() {
cout << "Moving."; }
private:
bool hungry;
int age;
int weight;
int
SensoryOrgan;
}
In the same way, an Employee object's
"has-a" relationship would consist of the attributes: Social
Security Number, Name, Age, Pay Rate, etc. Its methods might
consist of: Hire(), Fire(), PayRaise(), setSSN(), etc.
II. The "Is-a" Relationship
In the "is-a" relationship, objects that are grouped into larger,
macroscopic classifications are broken down in to smaller and smaller
groups. A Cat object is a Mammal object, a Mammal object is an
Animal object. Derivation is a way of explaining the "is-a" relationship. The
Iguana class would inherit from the Reptile class, and the Reptile
class from the Animal class. As an illustration, you don't have to state explicitly that Dogs move,
eat, or reproduce - they inherit that from Mammal. We might later
choose to override these Mammal methods with ones more specific to Dog,
but Dog inherits these methods in basic form built-in from its base
class, Mammal. A class that adds new functionality to an existing class is said to "derive" from that class. The original class
is said to be the new class's "base class". In Java, they would be
called subclass and superclass - in C++, base class and derived class. Typically a base class has more than 1 derived class.
Many of the examples in this section, due to lack of space and time and
for the sake of brevity, use what is referred to as "stubbing out".
This is writing only enough code to show that the function was called and leaving the details for later when you have more time.
This is one way in which you can build scaffolding for your
applications, around which you will later construct the entire program.
When declaring a class, indicate what class it derives from by writing a colon after the class name, the type of derivation (public,
private or protected), and the class from which it derives.
Example: class
Dog : public Mammal .
The class from which you derive must have been declared earlier in your
code or you will get a compiler error for trying to derive from a class
that the compiler does not know exists. Example:
//Simple inheritance
#include <iostream.h>
enum BREED { JollyGreen,
Friendly, Mean, Cannibalistic, Vegetarian, Mutant };
//--------------------------------------------------------------------------------------------------------------------
class Monster
{
public:
Monster();
~Monster();
//Accessor methods
int GetStrength()const;
void
SetStrength(int str);
int GetWeight()
const;
void
SetWeight(int wgt);
//Other methods
void Speak();
void Attack();
protected:
int Strength;
int Weight;
};
//--------------------------------------------------------------------------------------------------------------------
class Giant
: public Monster
{
public:
//Constructors
Giant();
~Giant();
//Accessors
BREED GetBreed()
const;
void SetBreed(BREED
thebreed);
//Other methods
FeFiFoFum();
FallDownTheBeanStalk();
protected:
BREED
itsBreed;
};
//--------------------------------------------------------------------------------------------------------------------
void main() {
} |
No Output.
In this first example, the Monster class declared. In C++ you can represent only a fraction of the information needed to represent reality, reality is far too complex to include
both every base and derived class of Monster, so you include only those
that will map back to reality reasonably well. In this case, the Giant class inherits from
Monster. Every Giant object will have 3 member variables: itsAge, itsWeight, and itsBreed
- the combination of the data members of the base and derived class.
The class declaration for Giant does not include itsAge and itsWeight, it inherits these from
Monster along with all of Monster's methods except the copy constructor, constructor and destructor.
This brings us to the inevitable discussion of three type of inheritance
- that is to say three type of derivation:
1) public - Public data members and methods are available to all
classes, both derived and non-derived. They may be accessed
without accessor methods. Because of this, though easiest to code
and debug, this is the least preferred method to code inheritance.
2) protected - Protected data members are available to derived
classes without having to use accessor methods, but they are not
available to any other classes that have not derived specifically from
the base class declaring its member to be protected.
3) private - Private data members are not available to derived
classes. If there is a public accessor method available in the
base class, the derived class may use it to manipulate private data
members. If not, the base class private data members and methods
are simply unavailable.
//Example of
protected
#include <iostream.h>
enum EmpType { LevelOne,
LevelTwo, LevelThree };
//--------------------------------------------------------------------------------------------------------------------
class Employee{
public:
//Constructor and destructor
Employee():PayRate(6.55), SSN(000000000) {
cout << "\nAn Employee has been created!\n"; }
~Employee() {cout << "Employee object being
destroyed."; }
//Accessor
methods
float GetPayRate()const
{ return PayRate; }
void SetPayRate(float
pay) { PayRate = pay; }
long GetSSN()
const {
return SSN; }
void SetSSN(long
social) { SSN = social; }
//Other
methods
void Promote()const
{ cout << "Give them a pay raise!\n";
cout << "Employee moved up one level in the food
chain!\n"; }
void Demote()const
{ cout << "Bump them down a level.\n"; }
protected:
long SSN;
float PayRate;
};
//--------------------------------------------------------------------------------------------------------------------
class Manager
: public Employee {
public:
//Constructor sets EmpType to LevelOne as default
Manager():itsEmpType(LevelOne){
cout << "A Manager that derives from an Employee has
been created!\n";}
~Manager(){ cout << "Manager object being
destroyed."; }
//Accessors
EmpType GetEmpType() const
{ return itsEmpType; }
void
SetEmpType(EmpType EType) { itsEmpType = EType; }
//Other
methods
void Hire() { cout
<< "It's your lucky day!\n"; }
void Fire() { cout
<< "Don\'t let the door hit you on the way out.\n";
}
private:
EmpType itsEmpType;
};
//--------------------------------------------------------------------------------------------------------------------
int main()
{
Employee CharlesGermany;
cout << "Employee CharlesGermany makes $" <<
CharlesGermany.GetPayRate()
<< " per hour!\n";
CharlesGermany.SetSSN(345768976);
cout << "CharlesGermany\'s social is: " <<
CharlesGermany.GetSSN() << ".\n\n";
Manager MyWonderfulBoss;
MyWonderfulBoss.Hire();
MyWonderfulBoss.Promote();
MyWonderfulBoss.SetSSN(265235687);
cout << "MyWonderfulBoss\'s social: " <<
MyWonderfulBoss.GetSSN() << ".\n";
MyWonderfulBoss.SetPayRate(65.25);
cout << "Manager MyWonderfulBoss makes $" <<
MyWonderfulBoss.GetPayRate()
<< " per hour!\n\n\n";
return 0;
} |
Output:
An Employee has been created!
Employee CharlesGermany makes $6.55 per hour!
CharlesGermany's social is: 345768976.
An Employee has been created!
A Manager that derives from an Employee has been
created!
It's your lucky day!
Give them a pay raise!
Employee moved up one level in the food chain!
MyWonderfulBoss's social: 265235687.
Manager MyWonderfulBoss makes $65.25 per hour!
Manager object being destroyed.
Employee object being destroyed.
Employee object being destroyed. |
In this example,
Employee is the base class and Manager derives from Employee.
We can see that when we instantiate the Employee object,
CharlesGermany, that is may use all of the methods and data
members of the Employee class. CharlesGermany may not,
however, use any of the manager methods or members.
Inheritance flows from the parent to the child, not vice versa.
On the other hand, the Manager class derives from the Employee
class. In this case it can use both the methods and
members of the Manager class and the Employee class.
Notice that each time a Manager is created, and Employee is
created as well. Calling the Manager constructor calls the
Employee constructor. It is the same with destructors.
Notice that as the program ends, it calls the destructor for
both a Manager object and an Employee object, then it calls the
destructor for and Employee object. This is the reverse
order which the constructors were called in as the objects were
created. This LIFO (Last In First Out) illustrates the
storage method of objects on the stack.
Passing arguments to base constructors -
In the case of the Mammal class, you may want to overload the Mammal constructor to take a specific age and the Dog constructor to take a breed. What if
Cats want to initialize weight, yet Mammals don't? Base class initialization can be performed during class initialization by writing the base class name followed by the parameters expected by the base class. Example:
//Overloading constructors in derived classes
#include <iostream.h>
enum BREED {
RussianBlue, Persian, Siamese, Tabby, TortoiseShell,
Himilayan };
//--------------------------------------------------------------------------------------------------------------------
class Mammal
{
public:
//2
overloaded constructors
Mammal();
Mammal(int age);
~Mammal();
//Accessors
int GetAge()
const {
return itsAge; }
void SetAge(int age) { itsAge = age; }
int GetWeight()
const {
return itsWeight; }
void SetWeight(int weight) { itsWeight = weight; }
//Other methods
void Speak() const { cout << "Mammal
noise.\n"; }
void Sleep()
const { cout << "Sleeping.\n"; }
protected:
int itsAge;
int itsWeight;
};
//Define 2
overloaded Mammal Class constructors
Mammal::Mammal():
itsAge(1),
itsWeight(5)
{
cout << "Mammal constructor...\n";
}
Mammal::Mammal(int age):
itsAge(age),
itsWeight(5)
{
cout << "Mammal(int) constructor...\n";
}
Mammal::~Mammal()
{
cout << "Mammal destructor...\n";
}
//--------------------------------------------------------------------------------------------------------------------
class Cat
: public Mammal
{
public:
//Overloaded Constructors
Cat();
Cat(int age);
Cat(int age,
int weight);
Cat(int age, BREED breed);
Cat(int age,
int weight, BREED breed);
~Cat();
//Accessors
BREED GetBreed() const {
return itsBreed; }
void SetBreed(BREED breed) { itsBreed = breed; }
//Other methods
void
ChaseMice() { cout << "Here mousie mousie ... \n"; }
void
ScratchFavoriteChair() { cout << "Destroying
expensive furniture ...\n"; }
private:
BREED itsBreed;
};
//Define 5
overloaded Cat class constructors
Cat::Cat():
Mammal(),
itsBreed(RussianBlue)
{
cout << "Cat constructor...\n";
}
Cat::Cat(int age):
Mammal(age),
itsBreed(RussianBlue)
{
cout << "Cat(int) constructor...\n";
}
Cat::Cat(int age,
int weight):
Mammal(age),
itsBreed(RussianBlue)
{
itsWeight = weight;
cout << "Cat(int, int) constructor...\n";
}
Cat::Cat(int age, BREED breed):
Mammal(age),
itsBreed(breed)
{
cout << "Cat(int, BREED) constructor...\n";
}
Cat::Cat(int age,
int weight, BREED breed):
Mammal(age),
itsBreed(breed)
{
itsWeight = weight;
cout << "Cat(int, int, BREED) constructor...\n";
}
Cat::~Cat()
{
cout << "Cat destructor...\n";
}
//--------------------------------------------------------------------------------------------------------------------int main()
{
Cat Felix;
Cat Tiger(5);
Cat Feebie(6,8);
Cat Princess (7,Persian);
Cat Killah (4,42,Siamese);
Felix.Speak();
Tiger.ChaseMice();
cout << "Princess is " << Princess.GetAge() << " years old\n";
cout << "Killah weighs " << Killah.GetWeight() << " pounds\n";
return 0;
} |
Output:
Mammal constructor . . .
Cat constructor . . .
Mammal(int) constructor . . .
Cat(int) constructor . . .
Mammal(int) constructor . . .
Cat(int, int) constructor . . .
Mammal(int) constructor . . .
Cat(int, BREED) constructor . . .
Mammal(int) constructor . . .
Cat(int, int, BREED) constructor . . .
Mammal noise.
Here mousie mousie ... |
Princess is 7 years old.
Killah weighs 42 pounds.
Cat destructor . . .
Mammal destructor . . .
Cat destructor . . .
Mammal destructor . . .
Cat destructor . . .
Mammal destructor . . .
Cat destructor . . .
Mammal destructor . . .
Cat destructor . . .
Mammal destructor . . . |
In this case, Cat's default constructor calls Mammal's default constructor.
We call the base class constructor which takes no parameters in order to
supply default values for Cat's base class member variables. All
of this overloading in the midst of inheritance brings us to the process
of overriding functions.
©2004 C. Germany
|