Inheritance and polymorphism

advertisement
Chapter 8 Inheritance and
Polymorphism; Chapter 19 Class
Hierarchies.
Base class
Class with items and methods all
explicitly defined.
Derived class
Class having access to methods and
items of a base class plus items and
methods of its own.
Derived class inherits stuff from the base
class.
See simple syntax on p. 342.
Any classes can be derived from a
single base class, reducing the
amount of extra coding.
Inheritance should model isa
relationships.
This is NOT the same as saying isa
relationships should always be
modeled using inheritance.
See hierarchy diagram on page 728.
Design software that manages bank
accounts: Checking, Savings, CD are three
possible types.
Each is different => 3 different classes. This is
inefficient because they all share much.
Define one base class that contains common
items: Account #, name, address, SS#, interest
rate; etc.
Each of the checking, savings, and CD extends
a base account class via the isa relationship.
i.e. a checking account isa bank account.
Design three separate classes that
extend the base class, each of which
has what is unique to the class. Some
examples:
Checking: Number with which next check
order begins; List of checks cashed.
CD: expiration date, penalty for early
withdrawal.
Savings: Various rates depending on
balance in account.
See demo10. Note how derived class
objects have access to methods in
BOTH the derived and base classes.
NOTE also the use of an overloaded <<
operator for every class and which
methods are called from the main.
What happens if you specify a base
class and a method of one of the derived
classes? i.e. p.getCredits() in demo10.
Suppose d is a derived object and f a
method.
If f is defined in d then d.f() calls it.
If f is defined in the base class then
d.f() calls the base class method.
NOTE that f can be defined in BOTH.
In this case, the derived class method
is called.
See bullets on p. 352.
Demo10 illustrates some examples.
NOTE the word public prior to the base
class in the declaration of a derived class.
Need it or member functions of a base
class can NOT be specified with an object
declared as a derived class type.
This would constitute private inheritance
and is rarely useful (common error on p.
348).
Calling a base class constructor.
See syntax on p. 349 and in demo10.
Derived class can NOT access
private members of its base class.
In the student class constructor put
name_ = “joe” and see the compile error.
In the person class change private to
protected and try again.
Protected means access from with
the class and all derived classes.
Designers recommend against using
protected access.
Designer of the base class loses
control over what objects can modify
a protected variable and any
constraints that may be needed.
See common error p. 355. Note what
happens if
Person::testFunc(outstream);
is changed to
testFunc(outstream);
in the student class testFunc method.
Infinite recursion and stack overflow.
Discuss this.
Polymorphism.
Consider the vector of objects from
demo10.
Need to have a default Person
constructor with no parameters to do
this.
Also need #include <vector>.
When the test harness is run, note
which testFunc is called throughout
the test harness (Each class has
one).
Note that different objects can be
added to the vector.
Compiler treats a Student object like a
Person object.
Makes sense since a Student isa
Person, so the compiler accepts it.
Similar for a Faculty object.
Note which instances of testFunc are
called in the vector loop.
Why?
Insert the code
directory [1].setCredits(123).
Since the record in position 1 is a
student, this would seem logical.
Compiler will not accept.
Why?
Possible solution?
Try a vector of student objects (vector
<Student> directory (10);).
Won’t even compile if the test harness
attempts to pushback a person or
faculty object.
A Person isnota Student necessarily.
Another issue!
Re-examine the vector of student
objects through the debug window.
The extra fields for Student and
Faculty are not even stored in the
vector since vectors are fixed sized
things.
This is called object slicing (p. 363).
So how do we get a list of all different
types?
SOLUTION: Vector of pointers to
objects (Polymorphic array).
vector <Person*> directory (0);
Demo11.
Step through demo11 and examine
the vector through the debug window.
Everything is there.
Examine the results generated by the
loop to display each object’s data.
Everything is NOT there.
Only Person object information
appears.
Only Person Object methods are
being called.
Why?
Code to generate method calls
determined at compile time. Compile
time binding or static binding.
The code tells the compiler that the
vector holds pointers to Person
Objects.
The compiler obliges by binding the
method calls to those in the Person
class
Solution: Put virtual prior to all of the
write declarations.
Only in the header file.
This makes them virtual.
That is, code to generate method calls
determined at run time.
Run time binding or dynamic binding.
Another form of Polymorphism –
referring to a collection of different
things.
See syntax, p. 363.
Virtual must be used in base class.
Supplying a virtual qualifier to all
methods that are named the same as
a virtual method in the base class is
not required but is considered good
form.
Step through demo11.
Problem:
Still cannot code
directory[1]setCredits(123) for
example.
Compiler does not recognize the
setCredits() method.
Solution:
Create a dummy virtual setCredits()
method in the person class and make it
virtual.
It need do nothing.
Note what happens if you write
directory[3]setCredits(123) (since item 3
is NOT a student – defaults to the Person
setCredits() method.
A bit of a Kludge
not really a good design but it worked anyway.
Alternative:
Set up a temporary student pointer to a
record and use it to call setCredits.
Example:
Student* temp = (Student*) directory[1];
temp  setCredits(45);
However, this requires advance
knowledge (at code writing time) that
dept[1] is a student.
How can you determine what type of
record you have in a polymorphic list?
A virtual isa() function can return
an object type (for example - a
string representing the type).
Then use conditional statements.
C++ provides other options.
dynamic casting and the typeid
operator.
In Visual C++ .NET must make sure
that “Enable run-time type info” is set
to “Yes” under Solution properties 
Configuration properties  C/C++ 
Language.
You will get compiler errors otherwise.
dynamic casting (p. 731):
Assume: B is a base class, D is a
derived class, pb points to a base
class and pd points to a derived class.
can write pd = dynamic_cast<D*> pb.
If pb happens to point to a object of type
D, this works.
If not, pd is NULL.
Can be used only to determine if a
variable has a specific type.
typeid operator (p. 733):
Somewhat more general.
Assume
B is a base class, D is a derived class,
pb points to a base class and pd points
to a derived class.
typeid(pb) and typeid(*pb) returns the
type (See demo11)
NOTE: dynamic casting allowed ONLY with
a polymorphic class (a class with at least
one virtual method.
Remove all virtual qualifiers and the
program will not build.
[http://msdn.microsoft.com/enus/library/4k5yex0s(VS.71).aspx]
[http://groups.google.com/group/microsoft.
public.vc.language/browse_thread/thread/a
f3eceb4da348452]
Note the common errors on p. 734.
NOTE second common error, author
suggests using virtual functions in the
base class and overriding them. This
makes sense in his example since
give_raise makes sense in both
classes. There are other cases where
it would NOT makes sense. i.e.
putting in a virtual setCredits method
in a Person class. Choose wisely.
NOTE: on virtual destructors, p. 6178. Step through the end of demo11
with and without destructors declared
as virtual. Show the difference.
Pure Virtual Functions: Used when a
behavior attached to a base class but
implementations only possible in
derived classes. e.g. area() in a
shape class. See p. 731. Useful for
graphics programs.
Advance topic (virtual function tables)
on p. 735 outlines how polymorphism
is implemented.
Note the concept of pointers to
functions. This might be covered in
370.
Multiple Inheritance:
Quality Tip on page 742: Avoid it! It’s
confusing and there are other
approaches to take (nested classes and
Java’s Interface.
Languages such as Java and C# were
specifically designed w/o multiple
inheritance.
Inclass activity question (Graphics
application)
Design (interface only) a square and
rectangle classes. Include methods to
Calculate area
Set the dimensions and x or y coordinate
of the center
Declare these methods and the
constructors.
If a square is derived from a rectangle
it, in fact, still contains BOTH length
and width.
The square also inherits the
rectangle’s set methods for EACH of
the length and width.
Will allow the length and width to be
set independent of each other.
That’s a problem.
OK – I suppose we can override the
rectangle’s setWidth and setLength
methods with a Square’s setWidth
and setLength methods.
Each of these will set BOTH length
and width, making sure the length and
width are always the same.
This works, but now the square has
two methods that do the same thing.
Troubling – not a clean design.
How do you explain that to a user of
the class?
It’s also misleading as exhibited in the
following segment.
Square s;
s.SetWidth(1);
s.SetHeight(2); //negates the previous
// line
Suppose you accept the previous
solutions. Consider
void f(Rectangle r)
{
r.SetWidth(32); // calls Rectangle::SetWidth
}
Square s;
f(s);
What happens?
Even this can be fixed if we change
the function signature to
void f(Rectangle& r)
and make sure the set methods are
virtual
Point is that these solutions are the
result of trying to fix issues as
opposed to solid design.
Even still, there are problems.
Consider
void f(Rectangle& r)
{
r.SetWidth(5);
r.SetHeight(4);
assert(r.GetWidth() * r.GetHeight()) == 20);
}
This works if f is called with a
rectangle but NOT if a square was
passed.
Should the programmer assume that
changing the width of a rectangle
leaves the length unchanged?
Seems a reasonable assumption but
varies with the object passed.
Though a square isa rectangle, some
consider it bad design to have a
square class inherit from a rectangle
class.
Inheritance is designed to extend a
class (inheritance by extension).
Inheritance is not necessarily
designed to restrict a class
(inheritance by restriction)
Liskov Substitution Principle
It states that
FUNCTIONS THAT USE POINTERS OR
REFERENCES TO BASE CLASSES MUST BE
ABLE TO USE OBJECTS OF DERIVED
CLASSES WITHOUT KNOWING IT.
[http://www.objectmentor.com/resources/ar
ticles/lsp.pdf]
The previous function f is a violation
of the Liskov substitution principle.
Could fix this with some dynamic
typing in the base class method.
But that requires the base class to
have knowledge of the derived class.
May also require changes in the base
class if new derived classes are
created.
This is all contrary to what inheritance
should be – a clean and simple
extension to a base class.
What to do?
A square isa rectangle.
Geometrically, this is true
Behaviorally, this is NOT true.
Behavior of a Square object is NOT
consistent with the behavior of a
Rectangle object for which length and
width are independent of each other.
Interpret the isa relationship carefully
and appropriately!!
Download