Date post: | 18-Jan-2018 |
Category: |
Documents |
Upload: | rhoda-blankenship |
View: | 218 times |
Download: | 0 times |
CSCI 383
Object-Oriented Programming & Design
Lecture 19
Martin van Bommel
Fall 2010 CSCI 383 Lecture 19 M. van Bommel 2
Inheritance as Classification In one way, the is-a relationship is a form of
classification E.g., A TextFile is a type of File so class TextFile
inherits from class File
File
TextFile
Fall 2010 CSCI 383 Lecture 19 M. van Bommel 3
Inheritance as Classification But in the real world, most objects can be
categorized in a variety of ways. A person can be a Male Professor Parent
None of these are proper subsets of the other, Cannot make a single rooted inheritance
hierarchy out of them
Fall 2010 CSCI 383 Lecture 19 M. van Bommel 4
Inheritance as Combination Instead, real world objects are combinations of
many nonoverlapping categories, each category contributing something to the result
Note that we have not lost the is-a relationship; it still applies in each case
Male Professor
Parent
Fall 2010 CSCI 383 Lecture 19 M. van Bommel 5
Multiple Inheritance Modeling this behavior in programs seems to
call for the concept of multiple inheritance An object can have two or more different parent
classes and inherit both data and behavior from each
Male Professor Parent
Fall 2010 CSCI 383 Lecture 19 M. van Bommel 6
Multiple Inheritance It wouldn't be an exaggeration to say that
multiple inheritance has stirred more controversy and heated debates than has any other C++ feature
Yet the truth is that multiple inheritance is a powerful and effective feature - when used properly
What is a good model?
Fall 2010 CSCI 383 Lecture 19 M. van Bommel 7
Incomparable Complex Numbers A portion of the Smalltalk class hierarchy
Where do complex numbers fit?
Object
Boolean Magnitude Collection
Char Number Point Set KeyedCollectionTrue False
Integer Float Fraction
things that can be compared to each other
things that can perform arithmetic
Fall 2010 CSCI 383 Lecture 19 M. van Bommel 8
Possible Solutions Make Number subclass of Magnitude, but redefine
comparison operators in class Complex to give error message if used (e.g., subclassing for limitation)
Don't use inheritance at all - redefine all operators in all classes (e.g., flattening the inheritance tree)
Use part inheritance, but simulate others (e.g., use Number, but have each number implement all relational operators)
Make Number and Magnitude independent, and have Integer inherit from both (i.e., multiple inheritance)
Fall 2010 CSCI 383 Lecture 19 M. van Bommel 9
Possible Solutions
Magnitude Number
Char Integer Complex
Fall 2010 CSCI 383 Lecture 19 M. van Bommel 10
Another Example – Walking Menus A Menu is a structure charged with displaying
itself when selected by the user A Menu maintains a collection of MenuItems Each MenuItem knows how to respond when
selected A cascading menu is both a MenuItem and a
Menu
Fall 2010 CSCI 383 Lecture 19 M. van Bommel 11
Multiple Inheritance in C++ C++ supports multiple inheritance (i.e., a class
may be derived from more than one base class) C++ syntax:
class Derived: public Base1, public Base2, public Base3, protected Base4 { ... };
Example:class passengerVehicle { ... };class trainCar { ... };
class passengerCar: public passengerVehicle, public trainCar { ... };
Handout #5
Fall 2010 CSCI 383 Lecture 19 M. van Bommel 12
The Ambiguity Problem Circumstances:
Let Derived be inherited from Base1 and Base2 All feature names inside Base1 are distinct All feature names inside Base2 are distinct All feature names inside Derived should be distinct
Ambiguity problem: the same feature name X occurs both in Base1 and in Base2 The problem does not occur in single inheritance
If the same feature name occurs both in Derived and in Base, then no ambiguity occurs. Why?
Fall 2010 CSCI 383 Lecture 19 M. van Bommel 13
The Ambiguity Problem In handout #5, what would happen if we
attempted to print the data members of derived by accessing them individually instead of using the overloaded operator<<
cout << “Object derived contains:\n” << “Integer: “ << derived.getData() << “\nCharacter: “ << derived.getData() << “\nReal number: “ << derived.getReal() << “\n\n“;
Fall 2010 CSCI 383 Lecture 19 M. van Bommel 14
Coincidental vs. Inherent Ambiguity Coincidental ambiguity occurs when the
duplicated names Base1::X and Base2::X are unrelated Ambiguity is a coincidence Same name, distinct entities
Inherent ambiguity occurs when Base1 and Base2 derive from a common Base, in which X occurs Ambiguity is inherent Same name, same entity
Fall 2010 CSCI 383 Lecture 19 M. van Bommel 15
Ambiguity Resolution Approaches Forbid the inheritance as given. Force the designer
of Base1 and/or Base2 to resolve the ambiguity Unreasonable: Good names are rare. In many cases,
multiple inheritance marries together two distinct inheritance hierarchies, sometimes supplied by different vendors
Impossible: In cases of repeated inheritance Force the designer of Derived to resolve all
ambiguities In Eiffel, the compiler does not allow a class in which an
ambiguity exists. The designer must rename all ambiguous features
In C++, a good designer will override all ambiguous features. This is not always possible since one cannot override data members
Fall 2010 CSCI 383 Lecture 19 M. van Bommel 16
Ambiguity Resolution in C++ Scenario
An inherited function/data member is used (inside or outside the class)
The compiler checks that the name is defined in exactly one (direct/indirect) base class
Error message produced if name is defined in more than one class
Ambiguity should be resolved before compilation can proceed Use qualified names (::) to specify exactly one derived
member
Fall 2010 CSCI 383 Lecture 19 M. van Bommel 17
Ambiguity Resolution in C++ In handout #5, if we wanted to print the data
members of derived by accessing them individually instead of using the overloaded operator<<, we could use the scope resolution operator (::) to resolve the ambiguitycout << “Object derived contains:\n” << “Integer: “ << derived.Base1::getData() << “\nCharacter: “ << derived.Base2::getData() << “\nReal number: “ << derived.getReal() << “\n\n“;
Drawbacks Client should be aware of implementation details Late detection of errors
One Solution: Redefinition/Renaming
By redefinition we mean a change in the operation of a command such as what happens when a virtual method is overridden in a subclass
By renaming we simply mean changing the name by which a method is invoked without altering the command’s functionalityclass Derived:public Base1, public Base2 { public:...int getData() const {return Base1::getData();}char getCharData() const {return Base2::getData();}double getReal() const {return real;}...};
Problem with Redefinition Solution While the redefinition of the function getData
solves the problem when a Derived object is used in isolation, further problems may arise when we consider the implications of the principle of substitution
Suppose getData is defined as a virtual function in Base1, Base2, and Derived
Base2* b = new Derived(7,’A’,3.5);cout << b->getData();
// this may not be what was intended
Diamond of Death
Another common problem occurs when parent classes have a common root ancestor
Occurs in every large system Must occur if the inheritance hierarchy has a common
root
Does the new object have one or two instances of the ancestor?
Think of an example in which we would like to keep two instances
A
B C
D
Diamond of Death Classical example: In C++ standard library,
multiple inheritance is used to form the class iostream
Handout #6
ios
istream ostream
iostreamifstream ofstream
fstream
Approaches to Repeated Inheritance
Forbid the situation A diamond occurs naturally in many cases Demand complicates the design
Single copy of common base class Not always what we want
Multiple copies of common base class Not always what we want
Approaches to Repeated Inheritance
Either single or multiple copies of common base class
Eiffel’s approach: decision made by designer of D C++’s approach: decision made by designer B and C
A
B C
D
Approaches to Repeated Inheritance
In C++ this problem is overcome through the use of the virtual modifier in the parent class list
The virtual keyword indicates that the superclass may appear more than once in descendant classes of the current class but that only one copy of the superclass should be included
class ios { ... };class istream: virtual public ios { ... };class ostream: virtual public ios { ... };class iostream: public istream, public ostream { ... };
Planning for the Diamond Problemclass Person {
...void print(ostream& out){
// Print person data (e.g., name, gender, etc) }};
class Student: virtual public Person {...void print(ostream& out){
Person::print(out);// Print student data (e.g., courses, year, etc)
}};
Planning for the Diamond Problem
class Teacher: virtual public Person {...void print(ostream& out) {Person::print(out)// Print teacher data (e.g., salary, courses, etc)
};
class TeachingAssistant: public Student, public Teacher {...void print(ostream& out) {Student::print(out);Teacher::print(out);// Print assistant data (e.g., supervisor, etc)
};
Do you see any problem? Fix this problem (DONE IN CLASS)
Cross Delegation Handout #7 Notice how a class that DerivedOne knows
nothing about will supply the override of a virtual function invoked by DerivedOne::foo()
This “cross delegation” can be a powerful technique for customizing the behavior of polymorphic classes
Constructors and Virtual Base Class?
class V { public: V(const char* s){ cout << s;}
};
class B1: virtual public V{public: B1(const char* s):V(“B1”){
cout << s;}};
class B2: virtual public V{public: B2(const char* s):V(“B2”){
cout << s;}};
class D: public B1, public B2 { public: D(void):B1(“DB1”),B2(“DB2”) {}
};
What will be printed when instantiating object of class D?
Construction of a Virtual Base Class
Work around 1 Define a default constructor in V
Work around 2 Call V’s constructor directly from the constructor of D
Virtual bases are initialized by the initializer of the most derived class, other initializers are ignored
Virtual bases may be initialized in a constructor of a derived class even if they are not immediate base classes
Practically speaking, this means that when you create a class that has a virtual base class, you must be prepared to pass whatever parameters are required to call the virtual base class's constructor
This might mean that the most-derived class's constructor needs more parameters than you might otherwise think