You are on page 1of 31

LECTURE 11

Polymorphism
What is Polymorphism?
-Polymorphism is allowing objects of different types to respond
differently to the same method call.

-You have seen some obvious polymorphism in method/function


overriding.
- Method overriding is the idea that the definition for a method
declared in a superclass is overridden in a subclass.

-In order to override a superclass method, the method in the


subclass must have the same method name, same parameter list,
same return type, and same const-ness (const or not const).
Different objects invoke different version of methods with
the same name (speak())
class Animal {
public: How does C++ knows
void speak() { which method to call?
cout << "I'm an animal\n";
Through function call
}
};
binding
class Bird : public Animal {
public:
void speak() { // overriding
cout << "I'm a bird\n";
} Output:
}; I'm an animal
I'm a bird
int main() {
Animal a;
a.speak(); // call Animal::speak()
Bird b;
b.speak(); // call Bird::speak()
return 0;
};
Function Call Binding
• Connecting a function call to a function body is called binding
• Function call binding is the process of determining what block of function
code is executed when a function call is made.

class Animal { Memory


public:
void speak() { ... }
};
0x7723
class Bird : public Animal {
public:
void speak() { ... }
}; 0x77b4
int main() {
Animal a;
a.speak();
Bird b;
b.speak();
return 0;
};
Function Call Binding

There are 2 types of function call binding:

Early/Static/Compile-time binding: Binding is performed


during compile-time, and is decided by the compiler and
linker.
Late/Dynamic/Runtime binding: Binding occurs at
runtime, based on the type of the object calling the
method.
Static Polymorphism
It is the polymorphism that is implemented using static binding

The compiler determines beforehand what method definition will


be executed for a particular method call, by looking at the type
of the variable invoking the method
Static Polymorphism
Consider the following Animal hierarchy, subclass Bird does
not override superclass Animal move() method

class Animal { Animal


public:
void move() {
+ move()
cout << "Moving";
}
};
Bird
class Bird : public Animal {
};
Static Polymorphism
Call the move() method from an Animal object or a pointer to an Animal
object.

int main() {
Animal a;
a.move(); // cout "Moving"
...
}
Animal::move() is called.
No problem

int main() {
Animal* a = new Animal;
a->move(); // cout "Moving"
...
}
Static Polymorphism
Call the move() method from a Bird object or a pointer to an Bird object.

int main() {
Bird b;
b.move(); // cout "Moving"
...
}
Animal::move() is called.
No problem

int main() {
Bird* b = new Bird;
b->move(); // cout "Moving"
...
}
Static Polymorphism
Call the move() method for a Bird object using a pointer
to an Animal
int main() {
Bird* b = new Bird;
Animal* a = b; // Upcasting
a->move(); // cout "Moving"
...
}
Animal::move() is called.
No problem
int main() {
Bird b;
Animal* a = &b; // Upcasting
a->move(); // cout "Moving"
...
}
Static Polymorphism
• Static binding always occurs when subclass does not override
superclass method. It generally does not introduce problem.

In all previous examples, output is always "Moving" which is fine


since subclass Bird does not provide a different meaning/semantic
for the move() method.

• When subclass does override superclass method, static binding is


still used by default, but it may introduce semantic/logical/precision
problem when upcasting.

Consider if subclass Bird does provide a different


meaning/semantic for the move() method, static binding will fail to
adhere to the new meaning when upcasting.
Static Polymorphism
Now consider the following Animal hierarchy, subclass Bird does
override superclass Animal move() method

class Animal { Animal


public:
void move() { cout << "Moving"; }
+ move()
};

class Bird : public Animal {


public:
void move() { cout << "Flying"; } Bird
};

+ move()
Static Polymorphism
Call the move() method from an Animal object or a pointer to an Animal
object
int main() {
Animal a;
a.move(); // cout "Moving"
...
}
Animal::move() is called.
No problem

int main() {
Animal* a = new Animal;
a->move(); // cout "Moving"
...
}
Static Polymorphism
Call the move() method from a Bird object or a pointer to a Bird
object

int main() {
Bird b
b.move(); // cout "Flying"
...
}
Bird::move() is called. No
problem
int main() {
Bird* b = new Bird;
b->move(); // cout "Flying"
...
}
Static Polymorphism: Problem
Call the move() method for a Bird object using a pointer to an
Animal object (upcasting)

int main() {
Bird* b = new Bird;
Animal* a = b; // Upcasting
a->move(); // cout "Moving"
...
Animal::move() is still called but
}
Bird::move() is more
appropriate/precise/accurate in the
context. This happens when
int main() { upcasting
Bird b;
Animal* a = &b; // Upcasting
a->move(); // cout "Moving"
...
}
Static Polymorphism: Problem
Call the move() method for a Bird object using a reference to an
Animal object (upcasting)

Animal::move() is still called but


Bird::move() is more
appropriate/precise/accurate in the
context. This happens when
upcasting

int main() {
Bird b;
Animal& a = b; // Upcasting via reference
a.move(); // cout "Moving"
...
}
Static Polymorphism: Problem

The solution to the upcasting problem we just discussed is to


use dynamic binding.
Virtual Function
• In order to allow a method to be bound at run-time, it must be
declared as virtual in the superclass.

• By declaring a method as virtual in superclass, it is


automatically virtual in all subclasses.
class Animal {
public:
virtual void move() { // enable dynamic binding
cout << "Moving"; }
};

class Bird : public Animal {


public:
void move() { // automatically virtual
cout << "Flying"; }
};
Virtual Function
• When overriding a virtual method in a subclass, we generally
use the keyword virtual to remind ourselves that it is using
dynamic binding.

class Animal {
public:
virtual void move() {
cout << "Moving"; }
};

class Bird : public Animal {


public:
virtual void move() { // as reminder
cout << "Flying"; }
};
Dynamic Polymorphism
Upcasting via pointer. After making the move() method virtual.

int main() {
Bird* b = new Bird;
Animal* a = b; // Upcasting
a->move(); // cout "Flying"
...
}
Bird::move() is called now, which is
more appropriate/precise/accurate
in context
int main() {
Bird b;
Animal* a = &b; // Upcasting
a->move(); // cout "Flying"
...
}
Dynamic Polymorphism
Upcasting via reference. After making the move() method virtual.

Bird::move() is called now, which is


more appropriate/precise/accurate
in context

int main() {
Bird b;
Animal& a = b; // Upcasting via reference
a.move(); // cout "Flying"
...
}
Dynamic Polymorphism: Benefit
The primary benefit of dynamic polymorphism is it enables to write
codes that work for all objects from the same inheritance hierarchy
via upcasted pointer / reference.
Consider the following inheritance hierarchy:

class Animal {
public: virtual void move() { cout << "Moving"; }
};
class Bird : public Animal {
public: virtual void move() { cout << "Flying"; }
};
class Fish : public Animal {
public: virtual void move() { cout << "Swimming"; }
};
class Mammal : public Animal {
public: virtual void move() { cout << "Walking"; }
};
Dynamic Polymorphism: Benefit
We can write a single callMove() function that call the right version of
move() method for all objects in the animal inheritance hierarchy.
void callMove (Animal & a) {
a.move();
}
int main() {
Animal a;
Bird b;
Fish f;
Mammal m; Output:
callMove (a);
callMove (b); Moving
callMove (f); Flying
callMove (m); Swimming
return 0; Walking
}
Dynamic Polymorphism: Benefit
We can also write a loop that calls the right version of move()
method for all objects in the animal inheritance hierarchy.

int main() {
// Array of different animals
Animal* a[4] = {new Animal,
new Bird,
new Fish,
new Mammal};
for (int i = 0 ; i < 4; i++) Output:
a[i]->move();
return 0; Moving
} Flying
Swimming
Walking
Virtual Destructor
• Superclass destructor should always be virtual for delete operator
to call the destructors correctly
• Otherwise, delete operator will call only superclass destructor, not
the subclass destructor.
• This will cause only the base part of the object to be destroyed.

// Problem int main() {


class Super { Super* p = new Sub; // upcasting
public: delete p; // invoke destructor
~Super() { // non virtual return 0;
cout << }
"Super destroyed\n"; }
};
class Sub : public Super {
public: Output:
~Sub() { cout << Super destroyed
"Sub destroyed\n"; }
};
26

Virtual Destructor
// Solution
class Super {
public:
virtual ~Super() {
cout << "Super destroyed\n"; }
};
class Sub : public Super {
public:
virtual ~Sub() {
cout << "Sub destroyed\n"; }
};
Output:
int main() {
Sub destroyed
// upcasting
Super destroyed
Super* p = new Sub;
delete p; // invoke destructor
return 0;
}
Abstract Base Class
• An abstract class is a class that cannot be instantiated.

• A class that can be instantiated is called concrete class.


• All classes that we have learned so far are concrete
classes.

• The reason to make a class abstract is, due to some


requirements/constraints, we want to make it impossible to
create instance(s) of the class.

• To be an abstract class: a class must have at least one pure


virtual function.
Pure Virtual Function
• A pure virtual function is a method that is initialized to zero (0) in
its declaration and no implementation is given.

• Pure virtual function makes the class abstract and no instance of


the class can be created.

class Animal { // abstract class


public:
virtual void move() = 0; // pure virtual function
};
int main() {
Animal a; // ERROR: Animal is an abstract class
return 0;
}
Pure Virtual Function
• Subclass of an abstract class must override all of the superclass
pure virtual functions in order to become instantiate-able
class Animal { // abstract superclass
public:
virtual void move() = 0; // pure virtual function
};
class Bird : public Animal { // concrete subclass
public:
virtual void move() { // overriding pure virtual function
cout << "Flying"; }
};
int main() {
Animal *a = new Bird; // upcasting
a->move(); // Fine. cout "Flying"
return 0;
}
Pure Virtual Function
• Subclass must override superclass pure virtual function in order
not to become abstract itself.

class Animal { // abstract class


public:
virtual void move() = 0; // pure virtual function
};
class Bird : public Animal { // abstract class
};
class Eagle : public Bird { // concrete class
public:
virtual void move() { } // overriding
};
The End

You might also like