Polymorphism
Inheritance
- Define families of classes sharing a common interface
- We want to manipulate objects in the family independent of the particular subclass
- Pointers/references to base class can refer to objects of any derived class
- Dynamic binding: function calls determined at runtime, depending on actual type of object pointed/referred to
- This polymorphism available only with pointers/references
Pointer Rules
- Base class pointer can point to derived class object
derived class is-a base class- Can't call methods defined only in derived class
- Derived class pointer can't point to base class object
derived-class extensions not part of base class
Binding
- Determine which method implementation within a class hierarchy is invoked for a particular function call
- Static: compile-time
used when compiler can determine correct implementation
example: when objects are declared as base or derived class
Time t;
ZonedTime zt;
t.setTime(12, 30, 00); // base class function must be used
zt.setZonedTime(20, 15, 00); // only defined in derived class
t.printTime( ); // base class function must be used
zt.printTime( ); // derived class function must be used
- Dynamic: run-time
used when correct implementation depends on runtime behavior
example: when objects are accessed through a pointer/reference
must use virtual functions to support polymorphism
class Shape
{
public:
virtual void draw ( ) const = 0; // pure virtual;type-specific
void error ( ) const; // non - virtual; overriding
// won't support polymorphism
virtual string ObjectType ( ) const; // virtual; subclass can
// override
};
void Shape :: error ( ) const
{ cerr << ObjectType ( ) << " error!" << endl; }
string Shape :: ObjectType ( ) const
{ return "Shape" ; }
class Circle : public Shape
{
public:
void draw ( ) const; // must implement so can instantiate
string ObjectType ( ) const; // override Shape :: ObjectType ( )
// Both methods implicitly virtual (since virtual in Shape)
};
void Circle :: draw ( ) const
{ /* implement drawing a circle */ };
string Circle :: ObjectType ( ) const
{ return "Circle"; }
class Rectangle : public Shape
{
public:
void draw ( ) const; // must implement so can instantiate
string ObjectType ( ) const; // override Shape :: ObjectType ( )
};
void Rectangle :: draw ( ) const
{ /* implement drawing a rectangle */ };
string Rectangle :: ObjectType ( ) const
{ return "Rectangle"; }
class Triangle : public Shape
{
public:
void draw ( ) const; // must implement so can instantiate
string ObjectType ( ) const; // override Shape :: ObjectType ( )
};
void Triangle :: draw ( ) const
{ /* implement drawing a triangle */ };
string Triangle :: ObjectType ( ) const
{ return "Triangle"; }
void main ( )
{
Shape* s1 = new Circle ( ); // static: Shape*, dynamic: Circle*
Shape* s2 = new Rectangle ( ); // static: Shape*, dynamic: Rectangle*
Shape* s3 = new Triangle ( ); // static: Shape*, dynamic: Triangle*
s2 -> draw ( ); // Rectangle :: draw ( )
s1 -> error ( ); // Shape :: error ( ), calls Circle ::
ObjectType ( )
cout << s3 -> ObjectType ( ); // Triangle :: ObjectType ( )
Shape* array[3] = { new Rectangle ( ), new Circle ( ), new Triangle ( ) };
for ( int i=0; i<3; i++ )
array [ i ] -> draw ( ); // calls correct method for
each element
}
Polymorphic Functions
- Want to be able to write a single function to handle a class hierarchy
- Use pointer/reference parameters
- Dynamic binding is used to call appropriate functions on parameter objects
void drawShape ( Shape* p ) // pass via pointer
{
cout << "drawShape called on: " << p -> ObjectType ( ) << endl;
// always prints correct type name; ObjectType is virtual function!
p -> draw ( ); // automatically calls function for correct type
if ( /* error condition */ )
p -> error ( ); // calls Shape :: error ( ), non virtual
// but that calls correct virtual ObjectType
}
void drawShape ( Shape& p ) // pass via reference
{
cout << "drawShape called on: " << p . ObjectType ( ) << endl;
// always prints correct type name; virtual function!
p . draw ( ); // automatically calls function for correct type
if ( /* error condition */ )
p . error ( ); // calls Shape :: error ( ), non virtual
// but that calls correct virtual ObjectType
}
this and virtual functions
- Add a non-virtual drawMe ( ) function to the Shape class
void Shape :: drawMe ( ) const
{ draw ( ); }- Circle, Rectangle, etc. do not override this implementation
- Which draw( ) gets called in the following code?
Rectangle r;
r . drawMe ( );- Rectangle :: draw ( ) is invoked within Shape :: drawMe ( ) since the actual call is this -> draw( ); (this is a pointer)
- Same situation with Shape :: error ( ) invoking correct ObjectType ( )
Code Dependence
- Procedural code: easy for new code to call old functions
- Hard for old code to call new functions
- Object-oriented code: old code can dynamically bind to new code
- Old function calls do not need to be modified
Pass-by-value Restrictions
When function parameters are passed by value:
- Polymorphism isn't supported on function calls on parameter
- Derived class objects may be passed in, but only base class methods can be invoked on them
- Only base-class part of derived-class object is available to function ("slicing")
Slicing
- Occurs when a derived class object is used as a base class object
- When A is-a B, A objects have a B object as a part
- When an A is used as a B, only the B part is available
Time t;
ZonedTime z;
t = z; // " Time part " of z is " sliced off " and assigned to t
// t is still a Time, not a ZonedTime
// Only Time-implemented methods can be called on t
// Only Time members are a part of the t object
Polymorphism and Destructors
- When a derived class object is deleted with delete via a base-class pointer, base class destructor is called (if not virtual)
- If derived virtual functions allocated memory, reserved locks, etc., base class destructor doesn't know about it
- Declare a virtual destructor in any base class with virtual functions
- Then base class destructor invoked when derived class destructor invoked
Designing Hierarchical Classes
- Identify set of operations common to all subclasses; these become the interface
- Identify type-dependent operations; these become pure virtual functions to be implemented in subclasses
- Identify access control (public, private, protected) of each operation