2. Learning Objectives
To understand static polymorphism and dynamic
polymorphism
To understand the problem of static
polymorphism
To enable dynamic polymorphism via virtual
function
To understand the benefit of dynamic
polymorphism
To understand abstract class
To understand why virtual destructor is needed
2
3. What is Polymorphism?
-Polymorphism is allowing objects of different types
to respond differently to the same method call.
-Polymorphism occurs when there is function
overriding.
- In lecture 3, superclass Human has a speak
method and is overridden by subclass Student.
Invoking the speak method from Human has
different result than Invoking the speak method
from Student.
3
4. Different objects invoke different version of methods
with the same name (speak())
class Animal {
public:
void speak() { How does C++ knows
cout << "I'm an animaln"; which method to call?
} Through function call
}; binding
class Bird : public Animal {
public:
void speak() { // overriding
cout << "I'm a birdn";
} Output:
}; I'm an animal
I'm a bird
int main() {
Animal* a = new Animal;
a->speak(); // call Animal::speak()
Bird* b = new Bird;
b->speak(); // call Bird::speak()
delete a;
delete b;
4
}
5. 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() { ... }
}; Animal::speak()
0x7723
class Bird : public Animal { Definition
public:
void speak() { ... }
};
Bird::speak()
0x77b4
int main() { Definition
Animal* a = new Animal;
a->speak();
Bird* b = new Bird;
b->speak();
delete a;
delete b;
5
}
6. Function Call Binding
There are 2 types of function call binding
(polymorphism):
Early/Static/Compile-time
binding/polymorphism: Binding is performed
during compile-time, and is decided by the compiler
and linker based on the variable used to invoke the
method.
Late/Dynamic/Runtime binding/polymorphism:
Binding occurs at runtime, based on the type of the
object calling the method.
6
7. 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.
• We have been using static polymorphism thus far
without realizing it.
7
8. Static Polymorphism
Consider the following Animal hierarchy, subclass
Bird overrides superclass Animal's move()
method.
class Animal { Animal
public:
void move() { cout << "Moving"; }
+ move():void
};
class Bird : public Animal {
public:
void move() { cout << "Flying"; } Bird
};
+ move():void
8
9. Static Polymorphism
Call the move() method from an Animal object, an Animal
pointer/reference to an Animal object, a Bird object, or an Bird
pointer/reference to a Bird object, got overriding but no
upcasting => no problem
int main() {
Animal a1;
a1.move(); // cout "Moving".
Animal *a2 = new Animal; // Pointer.
a2->move(); // cout "Moving".
Animal& a3 = a1; // Reference. Animal::move() is called.
a3.move(); // cout "Moving". No problem
Bird b1;
b1.move(); // cout "Flying".
Bird *b2 = new Bird; // Pointer.
b2->move(); // cout "Flying".
Bird& b3 = b1; Bird::move() is called. No
b3.move(); // cout "Flying". problem
9
}
10. Static Polymorphism: Problem
Call the move() method from a Bird object using an
Animal pointer/reference, got overriding and got
upcasting => big problem.
int main() {
Bird* b1 = new Bird;
Animal* a1 = b1; // Upcasting via pointer.
a1->move(); // cout "Moving".
Bird b2;
a1 = &b2; // Upcasting via pointer.
a1->move(); // cout "Moving".
Animal& a2 = b2; // Upcasting via reference.
a2.move(); // cout "Moving".
delete b1;
} Animal::move() is still called but
Bird::move() is more
appropriate/precise/accurate in the
context. 10
11. Static Polymorphism: Problem
Consider the following inheritance hierarchy:
class Animal {
public: void move() { cout << "Moving"; }
};
class Bird : public Animal {
public: void move() { cout << "Flying"; }
};
class Fish : public Animal {
public: void move() { cout << "Swimming"; }
};
class Mammal : public Animal {
public: void move() { cout << "Walking"; }
};
11
12. Static Polymorphism: Problem 1
To make a function callMove() to support every class in
the animal inheritance hierarchy, we have to
write/overload the callMove() function for every class.
// 1 callMove() only.
void callMove (Animal & a) Output:
int main() {
{ ... Animal a; Moving
a.move(); Bird b;
} Moving
Fish f; Moving
// 4 callMove() for Mammal m;
Moving
// 4 different classes. callMove (a);
void callMove (Animal & a) callMove (b);
{ ... a.move(); } callMove (f);
Output:
void callMove (Bird & b) callMove (m);
{ ... b.move(); } }
Moving
void callMove (Fish & f)
Flying
{ ... f.move(); }
Swimming
void callMove (Mammal & m)
Walking 12
{ ... m.move(); }
13. Static Polymorphism: Problem 1
We can actually solve the problem in previous slides by
using template. However the downside is the template
function won't be limited to the Animal hierarchy anymore.
// template callMove().
// Work but not limited to Animal hierarchy.
template <typename T>
void callMove (T & a)
{ ...
a.move();
}
13
14. Static Polymorphism: Problem 2
We cannot use one for loop to call the correct version
of move() method in an array/vector of objects from
Animal hierarchy.
Current output:
int main() {
Moving
// Array of different animals.
Moving
Animal* a[4] = {new Animal,
Moving
new Bird,
Moving
new Fish,
new Mammal}; Preferred output:
for (int i = 0 ; i < 4; i++)
a[i]->move(); Moving
for (int i = 0 ; i < 4; i++) Flying
delete a[i]; Swimming
} Walking
14
15. Dynamic Polymorphism
• The solution to the upcasting problem we just
discussed is to enable dynamic polymorphism or
dynamic binding via the use of virtual function.
• Requirements for C++ dynamic polymorphism:
• There must be an inheritance hierarchy.
• The superclass must have a virtual method.
• Subclass must override superclass virtual
method.
• A superclass pointer/reference to a subclass
object (upcasting) is used to invoke the
virtual method.
15
16. 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"; }
}; 16
17. Virtual Function
We can use the keyword virtual at subclass' virtual
function 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() { // Optional, as reminder.
cout << "Flying"; }
};
17
18. Dynamic Polymorphism
After making the move() method virtual:
int main() {
Bird* b1 = new Bird;
Animal* a1 = b1; // Upcasting via pointer.
a1->move(); // cout "Flying".
Bird b2;
a1 = &b2; // Upcasting via pointer.
a1->move(); // cout "Flying".
Animal& a2 = b2; // Upcasting via reference.
a2.move(); // cout "Flying".
delete b1;
} Bird::move() is called now, which
is more
appropriate/precise/accurate in
the context.
18
19. 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"; }
}; 19
20. Dynamic Polymorphism: Benefit
We can write a single callMove() function that call
the correct version of move() method for all objects
in the Animal hierarchy.
void callMove (Animal & a) {
...
a.move();
} Output:
int main() {
Animal a; Moving
Bird b; Flying
Fish f; Swimming
Mammal m; Walking
callMove (a);
callMove (b);
callMove (f);
callMove (m);
20
}
21. Dynamic Polymorphism: Benefit
We can also write a loop to call the correct version
of move() method for all objects in the Animal
hierarchy.
int main() {
// Array of different animals.
Output:
Animal* a[4] = {new Animal,
new Bird,
Moving
new Fish,
Flying
new Mammal};
Swimming
for (int i = 0 ; i < 4; i++)
Walking
a[i]->move();
for (int i = 0 ; i < 4; i++)
delete a[i];
}
21
22. Abstract Class
An abstract class is a class that cannot be
instantiated.
A class that can be instantiated is called a 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
a pure virtual function.
22
23. Pure Virtual Function
A pure virtual function is a method that is
initialized to zero (0) in its declaration and no
implementation is given in the class.
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 a1; // ERROR: Animal is an abstract class.
Animal* a2 = new Animal; // ERROR: same reason.
...
}
23
24. Pure Virtual Function
Subclass of an abstract class must override all of
the superclass pure virtual functions in order to
become a concrete class (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; // Fine. create a Bird object.
a->move(); // cout "Flying“.
}
24
25. Pure Virtual Function
Subclass that does not override superclass' pure
virtual function is an abstract class too.
class Animal { // Abstract class.
public:
virtual greet() {}
virtual void move() = 0; // Pure virtual function.
};
class Bird : public Animal { // Abstract class.
public:
virtual void greet() { ... }
};
class Eagle : public Bird { // Concrete class.
public:
virtual void greet() { ... }
virtual void move() { ... } // Overriding.
};
25
26. Virtual Destructor
Superclass destructor should always be virtual in
order for the delete operator to call the destructors
correctly for an subclass object that is created via
upcasting.
Otherwise, the 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. }
cout <<
"Super destroyedn"; }
};
class Sub : public Super { Output: // Where is Sub's destructor?
public: Super destroyed
~Sub() { cout <<
"Sub destroyedn"; }
26
};
27. Virtual Destructor
// Solution
To ensure that
class Super {
subclass objects are
public: destroyed properly,
virtual ~Super() { make superclass
cout << "Super destroyedn"; } destructor virtual.
};
class Sub : public Super {
public:
virtual ~Sub() {
Output:
cout << "Sub destroyedn"; }
Sub destroyed
};
Super destroyed
int main() {
Super* p = new Sub; // Upcasting.
delete p; // Invoke destructor.
}
27
28. Good Programming Practices
Use inheritance to promote code reusability.
Use virtual function to describe common behavior
within a family of classes.
Use pure virtual function to force subclasses to
define the virtual function.
Use a pointer/reference to an abstract base class to
invoke virtual function, thus implementing dynamic
polymorphism.
28