(Latest Revision: Sun Oct 1 22:32:38 PDT 2000 )
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: According to our text "A derived class inherits all
the members of its base class, except the constructors and
destructor." I'm not so sure I believe this -- in any case it
is hard to resolve with other things. For example, in a test
program I was able to call the sphere class constructor from
inside the declaration of the ball class constructor. Let's put
this issue aside until we have more information.
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.