Chapter 14 Inheritance 0. Introduction Object-Oriented Programming (OOP) is a powerful programming technique. This technique is of great importance for several reasons. OOP enables the solution domain to be closer to the problem domain, in part, by providing solution domain modeling for actors and behavior in problems. It facilitates code reuse, and it helps the programmer to better manage complexity through better abstraction and encapsulation. In our earlier study of data abstraction, we identified objects and classes. In C++, an object is a variable that has functions (behavior) and data associated with it. We noted that classes encapsulate the essence of objects that are declared to be of class type. We characterized inheritance as creating a new class by "adding features to the features obtained from another class." We used the derivation of the ifstream class from istream, and the derivation of ofstream from ostream as examples of inheritance. We found that an object has identity (name, type), behavior (actions of the member functions), and state (the values of
48
Embed
Chapter 14 - Inheritancewps.aw.com/wps/media/objects/80/82902/SavitchIMCh14.doc · Web viewChapter 14 Inheritance 0. Introduction Object-Oriented Programming (OOP) is a powerful programming
This document is posted to help you gain knowledge. Please leave a comment to let me know what you think about it! Share it to your friends and learn new things together.
Transcript
Chapter 14
Inheritance
0. Introduction
Object-Oriented Programming (OOP) is a powerful programming technique. This
technique is of great importance for several reasons. OOP enables the solution domain to
be closer to the problem domain, in part, by providing solution domain modeling for
actors and behavior in problems. It facilitates code reuse, and it helps the programmer to
better manage complexity through better abstraction and encapsulation.
In our earlier study of data abstraction, we identified objects and classes. In C++, an
object is a variable that has functions (behavior) and data associated with it. We noted
that classes encapsulate the essence of objects that are declared to be of class type. We
characterized inheritance as creating a new class by "adding features to the features
obtained from another class." We used the derivation of the ifstream class from
istream, and the derivation of ofstream from ostream as examples of inheritance.
We found that an object has identity (name, type), behavior (actions of the member
functions), and state (the values of the variables defined in the class and set on behalf of
our object.).
This chapter studies the inheritance relationship that classes bear to other, that is, the “is
a” relation. This chapter enables the student programmer to use inheritance in problem
solving.
1. Outline of topics in the chapter
14.1 Inheritance Basics
Derived Classes
Constructors in Derived Classes
Instructor’s Resource Manual for Savitch Absolute C++ 05/08/23 Page 2Chapter 14 Inheritance
The protected Qualifier
Redefinition of Member Functions
Redefining versus Overloading
Access to a Redefined Base Function
Functions That Are Not Inherited
14.2 Programming With Inheritance
Assignment Operators and Copy Constructors in Derived Classes
Destructors in Derive Classes
Protected and Private Inheritance
Multiple Inheritance
2. General remarks on the chapter
14.1 Inheritance Basics
In the real world, objects exist in relation to one another. In solving a problem, we need
to be as close to the problem as we can, within an abstraction that allows us to retain only
the details to necessary to meaningful solution of our problem. Abstraction means that we
ignore details. Ignoring details simplifies the model so that we can solve the problem.
Retaining details makes our solution realistic. A meaningful solution means we have
balanced the details ignored to decrease the complexity with the essential details retained.
In a problem, we may find that objects are similar to other objects. The abstraction of the
commonality among objects in C++ is done with classes, and abstraction of the
commonality among objects that have distinguishing differences is expressed in C++ by
the notion of inheritance. The commonalities are encapsulated in the base class, the
distinctions are encapsulated in the derived class or classes, and the commonality is
passed from the base class to the derived class by the inheritance mechanism.
For example, consider a class of (general) vehicles. We may think of several kinds of
vehicle whose properties we list here.
vehicle that carries passengers (a car),
small vehicle that carries cargo in the open (a pickup truck),
Instructor’s Resource Manual for Savitch Absolute C++ 05/08/23 Page 3Chapter 14 Inheritance
vehicle that carries cargo in an enclosure (Van),
vehicle that carries cargo enclosed and is articulated (A tractor-trailer).
The Car, Truck, Van, and TractorTrailer classes could inherit their common
features from the class vehicle. (Perhaps the common features of vehicles are
having a housing for the operator, a frame, wheels, a steering mechanism, a motor and
transmission.) We see that inheritance can creates a new class, called the derived class,
that is an extension, or modification of one or more existing classes, called the base
classes1.
We speak of single inheritance in the situation where there is only one base class.
Multiple inheritance refers to the situation where there is more than one base class. We
will not discuss multiple inheritance further as the text barely mentions this topic.
In these cases, a car is a vehicle, so are pickups, vans, and tractor-trailers. We describe
this relationship as an "is-a" relation. A car "is a" vehicle, a pickup truck "is a" vehicle,
and so on. The class abstraction of vehicle is the base class and the vehicles that bear the
"is a" relation to the base class are derived classes. Each base class has features, that the
derived class inherits those features and adds its own, extra, added features to the base
class to form its distinctiveness. If we write this example in C++, we haveclass Vehicle {/* . . . */};
class Car : public Vehicle { /* . . . */};
class Truck : public Vehicle { /* . . . */};
class TractorTrailer public Truck {/* . . . */};
class StationWagon : public Car {/* . . . */ };
class Boat : public Vehicle {/* . . .*/};
In this, example, we could illustrate multiple inheritance by defining
class Emergency {/* . . . */}; and have had a class PoliceCar
inherit from both class Car and class Emergency. We could have a
class Ambulance that inherits from both class Truck and
class EmergencyVehicle. The PoliceCar and Ambulance would inherit features
from class Car and the derived class adds its distinguishing features. The text only 1 In the C++ FAQ, pages 101 and 102, Cline and Lomow warn against use of “is a specialization of” and “is a subset of” as heuristics for detecting inheritance. Neither of “is a specialization of” or “is a subset of” implies that Derived must support all the operations defined by Base. The authors suggest substitutability as the proper heuristic.
Instructor’s Resource Manual for Savitch Absolute C++ 05/08/23 Page 4Chapter 14 Inheritance
treats single inheritance, so we will not discuss this more general situation further. For
further information on multiple inheritance, see Lippman and Lajoie, C++ Primer, 3rd
edition or Stroustrup, The C++ Programming Language 3rd edition.
Terminology:
The roots of Object Oriented everything (Design, Analysis, Programming) are diverse.
The result is that terminology is equally diverse. From Simula 67 and Smalltalk come
object, class, superclass and subclass, methods and instance variables. From C++ we get
base class and derived class, member functions and member variables. From CLOS
(Common Lisp Object System) we get generic functions In C++ we have the template
facility.
The text follows Stroustrup’s usage in that it uses the terms base class and derived class,
member function and member variable, rather than superclass, subclass, method and
instance variable. Stroustrup remarks that he finds it counter intuitive to have a 'subclass'
that has more features than the 'superclass'.
Other terms that are used with derived class family relationships are described in the box,
“Parent and Child Classes” on page 590 of the text.
Access Specifiers in Inheritance
Suppose class derived has base class Base, as in the following code.
class Base {/* . . .*/};
class Derived : <access-specifier> Base
{ /* . . . */};
//where <access-specifier> is one of public, protected, private
Then the access that is accorded to member functions of class Derived depends on
the access-specifier selected. Unfortunately2, the access specifier for class inheritance
defaults to private3. My students sometimes forget the access specifier, with results that
2 “Unfortunately” is only an expression of my opinion. It is not to be confused with fact.
Instructor’s Resource Manual for Savitch Absolute C++ 05/08/23 Page 5Chapter 14 Inheritance
are disappointing and confusing. Caution the students against omitting the access
specifier in inheritance.
It should be noted that while private and protected inheritance is possible, neither
is much used.
All Base Class Members Are Inherited
The private base class members are inaccessible to any function defined in any
derived class. For this reason, it is an error to attempt to access base class private
members. For purposes of writing code, private base class members might as well not
be inherited.
The foregoing apparently to the contrary, there is a very small number of exceptions to
the rule that every member of the base class is inherited, including the private
members. The exceptions to this are constructors, destructors, and certain operators
overloaded as members. We discuss this in a later section.
If you don’t believe that the private members are inherited, remember that all function
members of the base class are also inherited. These necessarily access the private
members of the base class, so these members must be present. You can confirm this.
Write a base class B with some private members (and other members), then derive a
class D with no members from the base class. You will find that the size of the derived
class object has the same size as the base class object.
If the derived class has members added of a size that would violate alignment that your
CPU requires, the implementation may allocate memory to “pad” accesses to achieve the
required alignment. For example, suppose you are using VC++ under Windows. If the
base class has only one int member, the base class object will have size 4. If the derived
class adds a char member and an int member in that order, the size of the derived class
3 The access specifier for struct defaults to public. The text treats a struct much as if structs had only the attributes they have in C. It does not use struct member functions, for example. We will not pursue the use of inheritance with structs.
Instructor’s Resource Manual for Savitch Absolute C++ 05/08/23 Page 6Chapter 14 Inheritance
object will be 16. The derived class will inherit the two int variables from the base class
(8 bytes), add 1 for the char, and 3 for added padding, and 4 more for the int. As we
said, the padding is to align the memory access for the int member.
The Member Initializer List
Remind the students of the alternate initialization usage they saw first in Chapter 1, page
12 and 13.int count(0), limit(10);
double distance(5.723), zymurgyConstant(1.234);
This, for declaration of variables, is an exact equivalent to the following.
int count = 0, limit = 10;
double distance = 5.723, zymurgyConstant = 1.234;
When initializing data members in a class, it is possible to initialize inside the
constructor:
class A
{
public:
A();
// Other members
private:
int i;
int j;
};
A::A()
{
i = 0;
j = 1;
}
The preferred method is to use member initializer lists (also known as base class
initializer lists). Member initializer lists are only be used with the constructor definition,
not with the declaration within the class4.
4 If the definition of the constructor is placed in the class, inlined, then the initializer list is placed in the class, but this is not the usual practice in this book.
Instructor’s Resource Manual for Savitch Absolute C++ 05/08/23 Page 7Chapter 14 Inheritance
class A
{
public:
A();
// Other members
private:
int i;
int j;
};
A::A() : i(0), j(0)
{// body deliberately empty
}
Stroustrup says that member initializers are required in situations where the semantics of
initialization differs from the semantics of assignment. That is, member initializers must
be used for members that are objects of classes not having default constructors, for
const members, and for reference members.
In the C++ FAQs, page 302 ff. Cline and Lomow say,
"Initialization lists are usually a performance issue, although there are cases when
the initialization lists impact correctness. . . . As a general rule, all member
objects and base classes should explicitly appear in the initialization list of a
constructor. In addition to being more efficient than to use the default
initialization followed by assignment, using the initialization list makes the code
clearer. There is no gain for built-in types, but there is no loss either, so
initialization lists should be used for symmetry.
…
Nonstatic const data members are declared in the class body with a const prefix
and … must be initialized in the constructor’s initialization list. …[A reference
data member] must be initialized in the initialization list of each constructor. "
Instructor’s Resource Manual for Savitch Absolute C++ 05/08/23 Page 8Chapter 14 Inheritance
Constructors In Derived Classes
Suppose for purposes of this section that class D is derived with specifier public
from class B. (All our inheritance will be public, I will refrain from mentioning that
the derivation is public in what follows.)
An object of the derived class D has both pieces from class D and inherited pieces
from base class B. As such, a derived class object’s inherited pieces constitute a slice
that is a purely base class object. If you take these inherited pieces, and add the pieces
added in the inheritance, you get the whole derived class object. In this sense a derived
class object has more than one type, namely, its own type and its base class type.
The derived class constructor can only initialize the variables defined in the derived class
definition. Only base class constructor can initialize base class variables. Constructors are
not inherited, and there is no way to call a base class constructor from within the body of
a derived class constructor. How, then, can the base class pieces be initialized? If we do
not somehow call an appropriate constructor, then the base class default constructor will
be called.
The default constructor may not produce the results we want. If we want some other
constructor to initialize the base class parts of our derived class, we call the desired base
class constructor in the member initializer list, before the variables are initialized.
class B
{
public:
B();
// other members
private:
int b
};
B::B() : b()
{// implementation
}
class D : public B
{
public:
Instructor’s Resource Manual for Savitch Absolute C++ 05/08/23 Page 9Chapter 14 Inheritance
D();
// other members
private:
int d;
};
D::D() : B(), d(0)
{// other implementation
}
The initialization is done by the implementation, not in the constructor declaration in the
class definition.
The protected Qualifier
We have seen that a member of a class can be public, or private. We have seen that
If the access qualifier is public, then any function can call a function in this
section, and any function can read or write a data member in this section.
If the access qualifier is private, then only functions declared in the class (or
friends of this class) can call a function in this section. Global functions and members
of nonfriend classes cannot call private functions read or write private data
members.
To these, C++ adds the keyword protected.
A member in a protected section can be accessed by member and friend functions
of this class and by members and friends of any derived class. The text puts it in
this succinct way: “Except for derived classes (and derived classes of derived
classes), a member variable that is marked protected is treated the same as if it were
private.
Members defined in the protected section of a class are accessible in member and
friend functions of the class, and in member and friend functions of classes derived
from the class. These members are inaccessible in any other function.
There are those who object to the use of friend functions on the theoretical ground that
it “breaks the encapsulation”. Stroustrup is blunt in his remarks regarding this attitude
toward friend functions, but he is not kind to the protected qualifier.
Instructor’s Resource Manual for Savitch Absolute C++ 05/08/23 Page 10Chapter 14 Inheritance
Stroustrup remarks on the friend function concept on page 53 and following in The
Design and Evolution of C++, 1994, Addison Wesley.
“A friendship declaration was seen as a mechanism similar to that of one
protection domain granting read-write capability to another. It is an explicit and
specific part of a class declaration. Consequently, I have never been able to see
the recurring assertion that a friend declaration “violates encapsulation” as
anything but a combination ignorance and confusion with non-C++ terminology.”
Stroustrup is not so kind to the protected qualifier. In The Design and Evolution
of C++, page 301 and following, he says,
“Mark Linton was the main architect of Interviews [an X Windows toolkit]. … He
argued persuasively … that protected data was essential for the design of an
efficient and extensible … [X Windows] toolkit. …
“Five years later, Mark banned the use of protected data members in Interviews
because they had become a source of bugs. ‘… novice users poking where they
shouldn’t in ways they ought to have known better…’ They also seriously
complicate maintenance. …”
“In my experience there have always been alternatives to placing significant
amounts of information in a common base class for derived classes to use
directly. In fact, one of my concerns about protected is that it makes it too
easy to sue a common base the way one might sloppily have used global data.
“Fortunately, you don’t have to use protected data in C++; private is the
default for classes and is usually the better choice. Note that none of these
objections are significant for protected member functions. Use of protected
functions is a fine way of specifying operation for use in derived classes. …
“In retrospect, I think that protected is a case where ‘good arguments’ and
fashion overcame my better judgment … for accepting new features.”
My conclusion is that protected data members can be avoided so should not be used
but there is no problem with protected member functions or friend functions. I
Instructor’s Resource Manual for Savitch Absolute C++ 05/08/23 Page 11Chapter 14 Inheritance
conclude further that accessor and mutator functions intended to serve derived classes can
(and perhaps should) be protected member functions. If declared inline, then with
simple accessor/mutator functions have the efficiency of public access and the protection
of the C++ model.
Redefinition of Member Functions
If in a derived class a function appears with the exact same signature5 as a function in the
base class, then we say that the base class function has been redefined. A base class
object will call the base class definition and a derived class object will call the derived
class definition. 6
Redefinition Versus Overloading
Redefined functions have definitions with the same signatures. This is function
redefinition even if the one of the definitions is in a base class and the other definition is
in a class derived from the base class. It is quite common to refer to function redefinition
as hiding the base member. Stroustrup says “A name in a derived class hides any object
or function of the same name in a base class.
Overloaded functions have the same name but different same signatures. This is
function name overloading even if one of the definitions is in a base class and the other is
in a derived class.
Here is some sample code:
class B
{
public:
void f(arglist1){ /*implementation 1*/}5 The signature includes all information that participates in overload resolution: the types of the parameters, and, if the function is a class member, the signature includes constant or volatile qualification and the class of which the function is a member. See the ANSI C++ Standard, Chapter 1, page 2, section 1.3.10.6 Which definition is called when the member function is called through a pointer or a reference depends on the type of the pointer or reference, not on the object to which the pointer points or to which the reference refers. Modifying this behavior is a subject we study in the next chapter.
Instructor’s Resource Manual for Savitch Absolute C++ 05/08/23 Page 12Chapter 14 Inheritance
// 80 tons gross vehicle weight, 20,000 lbs tow capacity
Truck t3(t1), t2;
t2 = t1;
cout << "\nTruck T1 (constructed) data:\n";
cout << t1.getMfgrName() << endl;
cout << t1.getNumberCyl() << endl;
cout << t1.getOwner() << endl;
cout << t1.getLoadCapacity() << endl;
cout << t1.getTowingCapacity() << endl;
cout << "\nTruck T2 (assigned) data:\n";
cout << t2.getMfgrName() << endl;
cout << t2.getNumberCyl() << endl;
cout << t2.getOwner() << endl;
cout << t2.getLoadCapacity() << endl;
cout << t2.getTowingCapacity() << endl;
Instructor’s Resource Manual for Savitch Absolute C++ 05/08/23 Page 35Chapter 14 Inheritance
cout << "\nTruck T3 (copy constructed) data:\n";
cout << t3.getMfgrName() << endl;
cout << t3.getNumberCyl() << endl;
cout << t3.getOwner() << endl;
cout << t3.getLoadCapacity() << endl;
cout << t3.getTowingCapacity() << endl;
return 0;
}
// end testing application
5. Patient and Billing
This extends #4. Write classes Patient and Billing. Patient is derived from class
Person. Class Patient object has the patient's name (through inheritance from class
Person, and adds primary physician of type Doctor (Project #3). Class Billing
contains a Patient object as member to contain the patient's name, a Doctor object,
and an amount due of type double.
Provide a reasonable complement of constructors and accessor, overloaded assignment,
and a copy constructor.7
This is only qualitatively different from other problems in this section.
7 The default copy constructor and operator assignment do exactly what we need. In the real world, these should not be written. I recommend pointing out to the student that apart from the valuable learning experience, these should not be written in where the defaults work correctly.