(Latest Revision:
Sun Mar 13 19:51:12 PST 2005
)
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 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.
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. 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 methods 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 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 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() method 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.
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:
- When we create a derived class B of A, we can choose the type of
inheritance. Our choice does not affect what is accessible to the methods
of B. If C is a derived class of B, the type of inheritance A-->B
affects the access methods of C have to methods of A. You might say that
it regulates the transitivity of inheritance. Similarly, the type
of inheritance A-->B affects the access that instances of B have to the
members of A.
- Whatever may be the type of inheritance A-->B, the methods of B can
access the public and protected (but not private) members of A.
- 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
- Instances of B can access the public (but not private or protected)
members of A.
- Methods of C can access the public and protected (but not private)
members of A.
- Instances of C can access the public (but not private or protected)
members of A.
- 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
- Instances of B cannot access any (private, protected, or public)
members of A.
- Methods of C can access the public and protected (but not private)
members of A.
- Instances of C cannot access any of the (private, protected, or
public) members of A.
- 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
- Instances of B cannot access any (private, protected, or public)
members of A.
- Methods of C cannot access any of the (private, protected, or
public) members of A.
- Instances of C cannot access any of the (private, protected, or
public) members of A.
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.