(Latest Revision:
Mon Sep 28 12:01 PDT 2015
)
C++_4_Notes01_Inheritance
C++ Interlude 4 Notes on Inheritance
Notes on the text of C++ Interlude 4 -- 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 are supposed to
"respond."
For example if SoccerBall is a derived class of Sphere Sphere
\
\
SoccerBall
and both classes have member functions (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 software reuse: 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.
This example of inheritance from a previous edition of the text:
//THIS IS THE BASE CLASS (SPHERE)
class sphereClass
{
private:
double TheRadius; // the sphere's radius
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;
}; // end class
Here is how the derived class is declared -- less work to do here
because we get lots of "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)*/
{
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
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 (a 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;
}; // end class
Important characteristics:
- An instance of the derived class can invoke any
public member function 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. The
assumption is that the designer of the base class makes certain things
private in order to protect the integrity of the base class from all
users - including "users" which are member functions of derived classes.
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 member function of the base class --
sphereClass */
SetRadius(NewRadius);
/* calling a member function of the derived class --
ballClass */
SetName(NewName);
} // end ResetBall
void ballClass::DisplayStatistics() const
{
cout << "Statistics for a " << TheName << ":";
/* calling a member function 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 member function of the base class has
been re-defined in the derived class, it is the member function of the derived class
that takes precedence in the derived class. For example if ball is
of type ballClass then ball.DisplayStatistics() makes the member function of the
derived ball class run. On the other hand if sphere is of sphereClass then
sphere.DisplayStatistics() makes the member function of the base class run.
- Sometimes the compiler can "see" which member function needs to be called and
generates code to make the proper call. This is called static binding
or early binding. However it is possible for one pointer variable to
point to either a ball or a plain 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 a plain sphere. In this case the compiler should generate code that, at run time,
will figure out which version of the DisplayStatistics() member function is appropriate
to be executed by *p. This is called dynamic binding 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.
The Keywords Public, Private and Protected
How They Determine Characteristics of Class Members
(Data Members or Member Functions)
- Public members: The definitions of the
member functions of a class X may reference any of the public members of X.
If t is an object that is an instance of class X,
then users of t
may reference any public member of X.
- Private members: The definitions of the member functions
of a class X may reference any of the private members of X. Code that is not
part of the definition of X is not allowed to reference private members
of X. This is very strictly interpreted -- even code defining
member functions of a derived class is not allowed to have references
to private members of the base class.
- Protected members are restricted more than Public but less than
Private. The definitions of the member functions
of a class X may reference any of the protected members of X. If Y is a derived
class of X then definitions of the member functions of Y may also reference
any of the protected members of X.
Clients of a class are denied access to protected members.
So if t is an object that is an instance of class X or class Y, users of
t may not reference any protected members of X.
- 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.
How The Keywords {Public, Private and Protected}
Determine Kinds of Inheritance:
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 do things like insert an item into the bottom or the middle
of the stack, which would violate its last-in-first-out structure.