(Latest Revision:
Wed Apr 2 23:34:56 PST 2003
)
ch08_Notes01_Inheritance
ch08 Notes on Inheritance
Notes on the text of chapter 8 -- INHERITANCE
X
\
\
Y
A class Y that has been derived from a class X is all that X was,
and more. Y has additional data and/or function members.
However, Y may have its own versions of some of X's members. In
that case, the members of Y are the ones that "respond."
For example if SoccerBall is a derived class of Sphere and both
classes have methods called drawSelf then if I have a variable
mySoccerBall of type SoccerBall and
I make a call to
mySoccerBall.drawSelf(), it will cause the version of drawSelf
belonging to the SoccerBall class to be executed , and not the
version of drawSelf derived from the Sphere class. (Presumably
the result will be that the drawing will look more like a soccer
ball and not like a plain sphere.)
Exception: A derived class inherits all the members
of its base class, except the constructors and destructor.
The advantage of using inheritance is that
you have less to program if you create a new
class as a derived class of a pre-existing class. (Perhaps it is easier
for people programming long-term in some particular application area to
appreciate this advantage. )
For example, if you are using classes to model the acoustic
properties of musical instruments, you could possibly save a lot
of work by creating a class for a new instrument by treating it
as a derived class of a preexisting instrument -- maybe like a
new kind of horn being a derived class of horn -- all horns have
some characteristics in common.
Note: Good documentation of the base class must be available.
Otherwise programmers will not have enough information to know
how to create derived classes.
The example of Inheritance from the text:
//THIS IS THE BASE CLASS (SPHERE)
class sphereClass
{
public:
/* constructors. */
sphereClass();
sphereClass(double InitialRadius);
/* copy constructor and destructor will be supplied
by the compiler */
// sphere operations:
void SetRadius(double NewRadius);
double Radius() const;
double Diameter() const;
double Circumference() const;
double Area() const;
double Volume() const;
void DisplayStatistics() const;
private:
double TheRadius; // the sphere's radius
}; // end class
Here is how the derived class is declared -- less work to do here
because we get all the declarations from the base class "for
free"
const int MAX_STRING = 15;
class ballClass: public sphereClass
/* The line above says that we want ballClass to be
derived from sphereClass (publicly)*/
{
public:
ballClass(); // default constructor
/* Another constructor: Creates a ball with radius
InitialRadius and name InitialName. */
ballClass(double InitialRadius, const char InitialName[]);
/* copy constructor and destructor supplied
by the compiler */
/* It's important to notice what is NOT here: all the
member functions of sphereClass -- no need to re-declare
-- we get all that automatically. For example users
will be able to make calls like: ball.Volume() when
ball is an instance of ballClass (variable of type
ballClass). */
/* additional or revised operations: */
/* this operation is new in the derived class
Determines the name of a ball. */
void GetName(char CurrentName[]) const;
/* this operation is new in the derived class
Sets (alters) the name of an existing ball. */
void SetName(const char NewName[]);
/* this operation is new in the derived class Sets
(alters) the radius and name of an existing ball
to NewRadius and NewName, respectively. */
void ResetBall(double NewRadius, const char NewName[]);
/* this operation is re-defined in the derived
class. It displays the statistics of a
ball. */
void DisplayStatistics() const;
private:
/* this data member is a new one -- in addition to
TheRadius, which we get from the parent class */
char TheName[MAX_STRING+1]; // the ball's name
}; // end class
Important characteristics:
- An instance of the derived class can invoke any
public method of the base class -- for example if ball is of
type ballClass, a user can call ball.Volume().
- A member function of the derived class can not
directly access private data members or private function members of
the base class -- for example a function defined in the ball
class cannot directly change TheRadius. This rule is felt to be
necessary for protecting the integrity of the private members of the
base class.
EXAMPLES OF HOW NEW FUNCTIONS FOR THE DERIVED CLASS ARE
DEFINED IN THE *.cpp FILE.
/* New constructors make use of the constructor(s) of
the base class. Note initializer syntax using
constructor of sphereClass. But note that the name
of the constructor (and class) is used here -- not
the name of a variable -- so this initializer form
is different from the form use to initialize data
members of ballClass. */
ballClass::ballClass() : sphereClass()
{
SetName("");
} // end default constructor
/* Again use of initializer syntax and sphereClass
constructor call */
ballClass::ballClass(double InitialRadius,
const char InitialName[])
: sphereClass(InitialRadius)
{
SetName(InitialName);
} // end constructor
/* This new function's definition is typical of many --
no need to reference the sphereClass in any way
here. */
void ballClass::GetName(char CurrentName[]) const
{
strcpy(CurrentName, TheName);
} // end GetName
void ballClass::SetName(const char NewName[])
{
strcpy(TheName, NewName);
} // end SetName
void ballClass::ResetBall(double NewRadius,
const char NewName[])
{
/* calling a method of the base class --
sphereClass */
SetRadius(NewRadius);
/* calling a method of the derived class --
ballClass */
SetName(NewName);
} // end ResetBall
void ballClass::DisplayStatistics() const
{
cout << "Statistics for a " << TheName << ":";
/* calling a method of the base class -- sphereClass
Here the programmer had to use the scope resolution
operator to resolve the ambiguity caused because
ballClass has its own version of void
DisplayStatistics() const. */
sphereClass::DisplayStatistics();
} // end DisplayStatistics
- In cases where a method of the base class has
been re-defined in the derived class, it is the method of the derived class
that takes precedence in the derived class. For example if ball is
of type ballClass then ball.DisplayStatistics() makes the method of the
derived ball class run. On the other hand if sphere is of sphereClass then
sphere.DisplayStatistics() makes the method of the base class run.
- Sometimes the compiler can "see" which method needs to be called and
generates code to make the proper call. This is called static or early
binding. However it is possible for a pointer to point to either a ball or
sphere. A compiler may come across a reference such
as p-->DisplayStatistics() and not be able to determine at compile time
whether *p is a ball or plain sphere. In this case the compiler must generate code that, at run time, will
check the type of *p and jump to whatever version of the
DisplayStatistics() method is appropriate. This is called dynamic or
late binding. You will learn more about these issues when you take
Programming Languages and Compiler Theory.
- The constructor of the base class executes
first, then the constructor of the derived class -- for example
when
ballClass ball
is seen in a program, first the constructor of sphereClass runs,
then the constructor of ballClass.
- With destructors, the order is the
reverse: First the destructor of the derived class, then the
destructor of the base class.
Keywords Public, Private and Protected.
- Public members of a class can be accessed by any
user of the class.
- Private members of a class can be accessed only
by the class member functions. This is very strictly
interpreted -- even member functions of a derived class cannot access
private members of the base class.
- Protected members are restricted more than Public but less than
Private. Clients of a class are denied access
to protected members. Member functions of the class and member
functions of the derived classes have access to protected members of
the base class.
- Exception: You can declare a function F to be a "friend" of a class C. The effect is to
give F the same access that a member function
of C has.
Public, Private, and Protected Inheritance.
Kinds of Inheritance:
- Public Inheritance: Public and protected
members of the base class remain, respectively, public and protected
members of the derived class. (If A has public derived class B,
and B has derived class C then members of C and instances of C have
the same accesses to members of A as would members of B and instances
of B, respectively. B passes on "rights" undiminished.)
- Protected inheritance: Public and protected
members of the base class are protected members of the derived class.
(If A has protected derived class B, and B has derived class C
then members of C have the same accesses to members of A as would
members of B, but instances of C have no access to the members of A.
B passes on "rights" to member functions of derived classes, but not
to clients of derived classes.)
- Private inheritance: Public and protected
members of the base class are private members of the derived class.
(If A has private derived class B, and B has derived class C
then members of C and instances of C have no access to members of A.
B passes on no "rights.")
Normally you would use public inheritance when you
have an "is-A" relationship. A ball is a sphere, a kitchen is a
room, and so on. Here every characteristic of the base class is also a
characteristic of the derived class. Therefore when you give access to the
derived class there is no reason to exclude access to the base class.
If the relationship is a "has-a" relationship
but not a "is-a" relationship then do not
use inheritance. Instead give one class an instance of another class
as a data member. For example a ball-point pen has a ball, and a mammal
has a heart. The class representing a ball-point pen could have a data
member that is an instance of the ball class. A mammal class could be
given a heart class data member.
Normally you would use private inheritance when you
have a "as-a" relationship to represent. A stack can be thought of
as a list of a certain kind. You can implement a stack by inheriting from
a list class. However if you give access to the stack, you don't usually
also want to give access to the list base class because that would allow
the client to violate the LIFO structure of the stack.