Home | Projects | Notes > C++ Programming > Polymorphism
What is polymorphism?
Using base class pointers:
Takes class hierarchies to a new level and enables us to think more abstractly and not have to worry about details.
Static vs. dynamic binding of function calls (pros vs. cons)
Virtual functions:
To achieve polymorphic functions (i.e., functions that are bound at run-time)
Virtual destructors
The override and final specifiers
Using base class references:
To achieve dynamic polymorphism.
Pure virtual functions and abstract classes:
Why we would want to use them as is and in interfaces.
Abstract classes as interfaces
Polymorphism is fundamental part of Object-Oriented Programming, and it allows us to think more abstractly when we write programs.
We can think deposit or print or draw, instead of thinking in specific versions of deposit, print and draw.
e.g., I can simply think 'deposit a $1000 to this account' not having to worry about what kind of account it is. I'll be sure that the correct deposit() function will be called depending on what type of account I have. And this is all determined at run-time.
Polymorphism
Compile-time polymorphism / early binding / static binding
Binding happens at compile time
e.g., Function overloading, operator overloading
Run-time polymorphism / late binding / dynamic binding
Binding happens at run-time
Requires:
Inheritance
Base class pointer or base class reference
Virtual functions
Run-time polymorphism
Being able to assign different meanings to the same function at run-time
Although static binding helps fast execution of the program, sometimes we want things to be decided when program is running.
In C++ we can achieve this behavior by using base class pointers or references and declaring our functions as virtual functions.
Allows us to program more abstractly
Think general vs. specific
Let C++ figure out which function to call at run-time
Not the default in C++, run-time polymorphism is achieved via
Inheritance
Base class pointers or references
Virtual functions

A non-polymorphic example - static binding

181// Assume that each type of account has its own 'withdraw()' function2
3Account a;4a.withdraw(1000); // calls Account::withdraw()5
6Savings s;7s.withdraw(1000); // calls Savings::withdraw()8
9Checking c;10c.withdraw(1000); // calls Checking::withdraw()11
12Trust t;13d.withdraw(1000); // calls Trust::withdraw()14
15Account *p = new Trust();16p->withdraw(1000); // calls Account::withdraw() 17 // but, we want the Trust object on the heap uses its own version18 // of withdraw (Trust::withdraw())For the functions that are not delared as
virtual, the compiler will, by default, perform static binding at compile time. At compile time, the compiler does not know what type of object the pointerpwill be pointing to at run-time. All it knows at compile time is thatppoints to anAccounttype object. So, the compiler will go ahead and bindAccount'swithdraw()function when it seesp->withdraw(1000);.
Let's see the following example:
181void display_account(const Account &acc)2{3 acc.display(); // regardless of what object was passed in this will always use 4 // Account::display() because of the way the parameter acc was5 // declared6}7
8Account a;9display_account(a);10
11Savings s;12display_account(s);13
14Checking c;15display_account(c);16
17Trust t;18display_account(t);Whatever the object passed into the function
display_account()is, the function will display only theAccountpart of the passed object.There is a way for C++ to ask the
Accountobject being passed in what kind of account they are, and then depending on that, we can haveif...elsestatements that call the appropriate display methods. That's a bad coding practice, and it also makes us program less abstractly since then we have to figure out what kind of object we have and then call its functions.
This is where the run-time polymorphism comes into play!
A polymorphic example - dynamic binding
Using virtual functions allow us to use run-time polymorphism when using base class pointers or references.

171// Assume that each type of account has its own 'withdraw()' function2
3Account a;4a.withdraw(1000); // calls Account::withdraw()5
6Savings s;7s.withdraw(1000); // calls Savings::withdraw()8
9Checking c;10c.withdraw(1000); // calls Checking::withdraw()11
12Trust t;13d.withdraw(1000); // calls Trust::withdraw()14
15Account *p = new Trust();16p->withdraw(1000); // calls Trust::withdraw() 17 -----The idea of using
virtualfunctions tells the compiler not to bind it at compile-time, but instead, defer the binding to run-time. And at run-time, a check will occur to see exactly whatpis pointing to and then that object's function will be called.
171void display_account(const Account &acc)2{3 acc.display(); // will now always call the display function depending on the4 // passed object's type at RUN-TIME!5}6
7Account a;8display_account(a);9
10Savings s;11display_account(s);12
13Checking c;14display_account(c);15
16Trust t;17display_account(t);The binding of
acc.display()will take place at run-time and call thedisplay()function based on the type of object being passed in.
This is very powerful!!!
I can now write functions, methods, and data structures that use pointers and references to base class objects, and I know that the correct functions will be called at run-time.
A non-polymorphic example - static binding
Following example addresses the limitation of static binding and the necessity for dynamic binding.
6312// smart pointer3
4class Base5{6public:7 void say_hello() const8 {9 std::cout << "Hello, I'm a Base class object" << std::endl;10 }11};12
13class Derived: public Base14{15public:16 void say_hello() const17 {18 std::cout << "Hello, I'm a Derived class object" << std::endl;19 }20};21
22void greetings(const Base &obj) // this parameter can take23{ // 1) Base class object24 std::cout << "Greetings: "; // 2) any object of Base class' derived class25 obj.say_hello(); // because derived class 'is-a' base class26
27 // whatever type of object is passed in, since say_hello() function is not virtual,28 // the compiler has no choice but to recognize obj as Base class object and therefore29 // obj.say_hello() will always call Base::say_hello()30}31
32int main(int argc, char *argv[])33{34 Base b;35 b.say_hello(); // compiler will see that this is not a virtual function (i.e., no36 // dynamic polymorphism associated with it), and it knows that it37 // needs to bind this statically (compiler only knows at compile-time38 // that b is the type of Base so it is going to bind Base class'39 // say_hello() function to this statement)40 Derived d;41 d.say_hello(); // similarly, the compiler will statically bind Derived class'42 // say_hello() function to this statement43
44 // problem occurs when using the base class pointer or reference45
46 greetings(b); // Greetings: Hi, I'm a Base class object47 greetings(d); // Greetings: Hi, I'm a Base class object48
49 Base *ptr = new Derived(); // ptr can hold address of any Base object and Derived50 // 'is-a' Base object (so this is perfectly legal)51
52 ptr->say_hello(); // but here, since say_hello() is not virtual, the compiler has to53 // bind it statically and since ptr is of type Base*,54 // Base::say_hello() will be bound to this statement55
56 // smart pointer is no exception57 std::unique_ptr<Base> ptr1 = std::make_unique<Derived>();58 ptr1->say_hello(); // Hi, I'm a Base class object59
60 delete ptr;61
62 return 0;63}61Hello, I'm a Base class object2Hello, I'm a Derived class object3Greetings: Hello, I'm a Base class object4Greetings: Hello, I'm a Base class object5Hello, I'm a Base class object6Hello, I'm a Base class object
Let's assume that the following example uses the dynamic polymorphism:

111Account *p1 = new Account();2Account *p2 = new Savings();3Account *p3 = new Checking();4Account *p4 = new Trust();5
6p1->withdraw(1000); // Account::withdraw()7p2->withdraw(1000); // Savings::withdraw()8p3->withdraw(1000); // Checking::withdraw()9p4->withdraw(1000); // Trust::withdraw()10
11// delete pointersNow, we can call the
withdraw()function using the base class pointers and C++ will figure out which function to bind at run-time based on the type of the object being pointed to by each pointer.
Using this feature, above code can be rewritten more concisely as follows:
111Account *p1 = new Account();2Account *p2 = new Savings();3Account *p3 = new Checking();4Account *p4 = new Trust();5
6Account *array[] = {p1, p2, p3, p4};7
8for (auto i = 0; i < 4; ++i)9 array[i]->withdraw(1000);10
11// delete pointersThis example shows what "Programming more abstractly or generally" means! Here, you are simply thinking 'call the
withdraw()function for each account in the array.' That's it! No more details required!Programming more abstractly!
Similarly, this code can be rewritten using vector and range based for loop as follows:
111Account *p1 = new Account();2Account *p2 = new Savings();3Account *p3 = new Checking();4Account *p4 = new Trust();5
6vector<Account*> accounts {p1, p2, p3, p4};7
8for (auto acc_ptr : accounts)9 acc_ptr->withdraw(1000);10
11// delete pointersThink about what would happen if we added another class to our account hierarchy, say a Bond account. None of the code that we have that works with account objects needs to be changed. Since a bond account is an account, it will automatically work with our existing code.
[!] Note: Be careful when you're using raw pointers and collections such as vectors. It is better to use smart pointers in this type of examples.
virtual Functionsvirtual functions in a base class MUST be "defined" unless they are declared using the pure specifier.
Redefined functions are bound statically
When we derive a class from base class, we can redefine the base class' functions behaviors in the derived class. This creates a specialized version of the function specific to the derived class.
If we don't use the virtual keyword with these functions, then they're statically bound at compile-time.
Overridden functions are bound dynamically.
We do this by declaring the function to be virtual.
virtual functions are overridden functions.
Allows us to think abstractly by treating all objects in the hierarchy as objects of the base class.
Once we declare a function as virtual, then that function is virtual all the way down the class hierarchy from this point forward.
Declaring virtual functions in the base class:
Declare the function you want to override as virtual in the base class
Virtual functions are virtual all the way down the hierarchy from this point
Dynamic polymorphism only via the base class pointer or reference
71// base class2class Account 3{4public:5 virtual void withdraw(double amount) { /* code */ };6 . . .7};This makes
withdraw()avirtualfunction which means it can be overridden in derived classes and will be bound dynamically at run-time when we use a base class pointer or reference.
Declaring virtual functions in the derived class:
Override the function in the derived classes
Function signature and return type must match EXACTLY
If does not match, the compiler will regard it as redefinition and statically bind it.
Once declared as a virtual function, the virtual keyword is no longer required but is best practice to specify it down the class hierarchy.
If you don't provide an overridden version it is inherited from it's base class
71// derived class2class Checking : public Account3{4public:5 virtual void withdraw(double amount) { /* code */ };6 . . .7};Remember! virtual functions are dynamically bound ONLY when they are called via a base class pointer or reference. Otherwise, they are statically bound.
Whenever you have virtual functions, you need to have virtual destructors.
virtual DestructorsProblems can happen when we destroy polymorphic objects.
e.g., Deleting a polymorphic object that doesn't have a virtual destructor could lead to unexpected behavior.
If a derived class is destroyed by deleting its storage via the base class pointer and the class does not have a virtual destructor, then the behavior is undefined by the C++ standard.
If this is the case, only the destructor of the pointer type class (i.e., base class) will be called and it is possible that the derived class' specific operations will not go through the proper termination process (e.g., writing buffers out, closing files, deleting pointers to the dynamically allocated memory space, etc.) which could potentially lead to a serious situation such as memory leak.
Derived objects must be destroyed in the correct order starting at the correct
If a class has a virtual functions, ALWAYS provide a public virtual destructor. If base class destructor is virtual then all derived destructors are also virtual. (No need to provide the virtual keyword again, but it's best practice to do so.)
171// base class2class Account3{4public:5 virtual void withdraw(double amount) { /* code */ };6 virtual ~Account(); // simple solution for the potentially serious situation7 . . .8};9
10// derived class11class Checking : public Account12{13public:14 virtual void withdraw(double amount) { /* code */ };15 virtual ~Checking(); // simple solution for the potentially serious situation16 . . .17};If none of these class had virtual destructor
41Account *p = new Checking();2
3delete p; // this will call the destructor of Account class ONLY! leaving behind 4 // some of the Checking object's attributes not taken care ofRule is simple!
If you've got a class and it's got a virtual function, give it a virtualdestructor as well.
FYI, there's no such thing as a virtual constructor and it doesn't even makes sense at all.
override SpecifierWe can override base class virtual functions.
The function signature and return type must be EXACTLY the same.
If they are different then we have redefinition NOT overriding.
This small mistake is really difficult to spot. So, be careful!
Redefinition is statically bound.
Overriding is dynamically bound.
C++11 provides the override specifier to have the compiler ensure overriding.
You can add this specifier to the functions you're writing in your derived class, and the C++ compiler will ensure that you're indeed overriding and not redefining.
This is a simple addition to the language but a very useful one.
Example:
201class Base2{3public:4 virtual void say_hello const5 {6 std::cout << "Hello - I'm a Base class object" << std::endl;7 }8 virtual ~Base() {}9};10
11class Derived : public Base12{13public:14 virtual void say_hello()15 {16 // Notice I forgot the const - NOT OVERRIDING!17 std::cout << "Hello - I'm a Derived class object" << std::endl;18 }19 virtual ~Derived();20};L14: Since the function signatures are not exactly the same, the C++ compiler considers this function redefinition and NOT overriding. It will compiler just fine since this is perfectly legal.
51Base *p1 = new Base();2p1->say_hello(); // "Hello - I'm a Base class object" (Bound dynamically)3
4Base *p2 = new Derived();5p2->say_hello(); // "Hello - I'm a Base class object" (Bound statically to base class' method)Not what we expected!
say_hello()function signatures are different, soDerivedredefinessay_helloinstead of overriding it!
We can easily prevent this error by using the C++11 override specifier.
191class Base2{3public:4 virtual void say_hello const5 {6 std::cout << "Hello - I'm a Base class object" << std::endl;7 }8 virtual ~Base() {}9};10
11class Derived : public Base12{13public:14 virtual void say_hello() override // Produces compiler error (Error: marked override but does not override)15 {16 std::cout << "Hello - I'm a Derived class object" << std::endl;17 }18 virtual ~Derived();19};L14: Now with the
oeverridekeyword, the compiler will generate an error that will help us fix the hard-to-spot issue.
final SpecifierC++11 provides the final specifier which can be used in two contexts.
When used at the class level, it prevents a class from being derived from or subclassed.
In some cases, this is done for better compiler optimization.
Other times, it may be done to ensure that objects are copied safely without slicing.
This is done for better compiler optimization.
When used at the method level, it prevents virtual method from being overridden in derived classes.
This can also be done for better compiler optimization.
Example - final keyword used at the class level
91class My_Class final // My_Class cannot be derived from2{3 ...4};5
6class Derived final : public Base // Derived cannot be derived from7{8 ...9};If you try to derive a class from these classes, the compiler will generate a compiler error.
Example - final keyword used at the method level
151class A2{3public:4 virtual void do_something();5};6
7class B : public A8{9 virtual void do_something() final; // prevent further overriding10};11
12class C: public B13{14 virtual void do_something(); // COMPILER ERROR - Can't override 15};
We can also use base class references and have polymorphic function calls as we could by using the base class pointers.
Useful when we pass objects to functions that expect a base class reference
Example of using references to achieve dynamic binding of virtual functions:

71Account a;2Account &ref = a;3ref.withdraw(1000); // Account::withdraw()4
5Trust t;6Account &ref1 = t;7ref1.withdraw(1000); // Trust::withdraw() <--- dynamic bound!Also,
101void do_withdraw(Account &account, double amount)2{3 account.withdraw(amount); // since withdraw() is a virtual function, it is bound dynamically4}5
6Account a;7do_withdraw(a, 1000); // Account::withdraw()8
9Trust t;10do_withdraw(t, 1000); // Trust::withdraw()
virtual FunctionsAbstract class
Cannot instantiate objects
These classes are used as base classes in inheritance hierarchies
Often referred to as Abstract Base Class
Too generic to create objects from
e.g., Shape, Employee, Account, Player, etc.
Serves as parent to derived classes that may have objects
Contains at least one pure virtual function
Concrete class
Used to instantiate objects from
All their member functions must be "defined"
e.g., Checking Account, Savings Account in an account hierarchy
e.g., Faculty, Staff in an employee hierarchy
e.g., Enemy, Level Boss in a player hierarchy
[!] Note: If there is an intermediary class that inherits the base class but does not implement (define) the pure virtual function(s) that it inherited, then that intermediary class is also abstract base class.
Used to make a class abstract
Specified with =0 in its declaration
11virtual void function() = 0; // pure virtual functionTypically do not provide implementations since it's really up to the derived concrete classes to define this behavior (But, it is possible to give them an implementation)
This is very useful when it doesn't make sense for a base class to have an implementation, but it does make sense in concrete classes.
Derived classes MUST override the pure virtual functions in the base class in order for it to be a concrete class.
If the derived class does not override then the derived class is also abstract
Used when it doesn't make sense for a base class to have an implementation (But, concrete classes must implement it)
11virtual void draw() = 0; // in the Shape classThe concept of a shape is too general or too abstract to have shape objects in our system. So, we can define the draw() function as a pure virtual function in the shape class, and that not only makes it an abstract class, but it also forces all derived classes to implement the draw function if they want to be concrete classes.
Same thing can be said about the Player class:
11virtual void defend() = 0; // in the Player classExamples
Following Shape class has pure virtual functions so it is abstract class and therefore cannot directly instantiate objects.
151class Shape // abstract class2{3private:4 // attributes common to all shapes5public:6 virtual void draw() = 0; // pure virtual function 7 virtual void rotate() = 0; // pure virtual function 8 virtual ~Shape();9 . . .10};11
12. . .13
14Shape shape; // Compiler ERROR: Can't instantiate an abstract class15Shape *p = new Shape(); // Compiler ERROR: Can't instantiate an abstract classFollowing Open_Shape class is still an abstract class because it does not override the virtual functions declared in its base (Shape) class.
51class Open_Shape : public Shape // abstract class2{3public:4 virtual ~Open_Shape() {}5};Following Circle class has no pure virtual functions so it is concrete class and therefore can instantiate objects. (All virtual functions of the base class are overridden.)
221class Circle : public Shape // concrete class2{3private:4 // attributes common to all shapes5public:6 virtual void draw() override // override keyword is not mandatory here7 {8 // code to draw a circle9 }10 virtual void rotate() override // override keyword is not mandatory here11 {12 // code to rotate a circle13 }14 virtual ~Circle();15 . . .16};17
18. . .19
20Shape *p = new Circle(); // OK21p->draw(); // OK22p->rotate(); // OKThe real power of the pure virtual functions is to really force all the derived concrete classes to implement those functions.
An interface class in C++ is a class that has only pure virtual functions.
These functions provide a general set of services that users of the class can use.
In order for them to be useful, these pure virtual functions must be declared as public.
Any class that wants to be a concrete class and support the interface provided by the interface class can override the pure virtual functions and implement these services as needed.
Remember, every service must be implemented, and C++ will strictly enforce type information. So, when overriding the function, prototypes must match exactly.
Unlike Java and C#, C++ does not provide a specific keyword or way to create an interface as part of the c++ core language. Therefore, in C++ we use abstract classes with pure virtual functions to achieve the concept of an interface.
Example 1
Suppose we want to be able to provide Printable support for any object we wish without knowing it's implementation at compile time.
11std::cout << any_object << std::endl;Then, any_object must conform to the Printable interface.
311class Printable 2{3 friend ostream& operator<<(ostream&, const Printable &obj);4 // this friend function will allow us to print any type of object that conforms5 // to this interface6public:7 virtual void print(ostream &os) const = 0;8 virtual ~Printable() {};9 . . .10};11
12// overloaded operator function13// - this would dynamically bind to the print() function of whatever object type was14// passed in since we know that that object is printable and we have a reference to15// it16ostream& operator<<(ostream &os, const Printable &obj)17{18 obj.print(os);19 return os;20}21
22class Any_Class : public Printable23{24public:25 // must override Printable::print()26 virtual void print(ostream &os) override27 {28 os << "Hi from Any_Class";29 }30 . . .31};[!] Note: Here the overloaded operator function is not a member function of Print class, and therefore will not be inherited down to the class hierarchy.
Now, Any_Class is printable. Let's create Any_Class objects and print them:
151Any_Class *p = new Any_Class();2cout << *p << endl;3
4void function1(Any_Class &obj)5{6 cout << obj << endl;7}8
9void function2(Printable &obj)10{11 cout << obj << endl;12}13
14function1(*p); // "Hi from Any_Class"15function2(*p); // "Hi from Any_Class"Example 2
Here, we have a Shape class that only has pure virtual functions. So, it's an abstract class, and it can also be used as an interface class.
81class I_Shape2{3public:4 virtual void draw() = 0;5 virtual void rotate() = 0;6 virtual ~Shape() {};7 . . .8};Sometimes you'll see classes that are intended to be used as interface classes names with a capital "I_" preceding the class name.
A concrete class Circle can be written as:
81class Circle : public I_Shape2{3public:4 virtual void draw() override { /* code */ };5 virtual void rotate() override { /* code */ };6 virtual ~Circle();7 . . .8};So, we can now use pointers to I_Shape obejcts to use dynamic polymorphism.
131vector<I_Shape*> shapes;2
3I_Shape *p1 = new Circle();4I_Shape *p2 = new Line();5I_Shape *p3 = new Square();6
7for (auto const &shape : shapes)8{9 shape->rotate();10 shape->draw();11} 12 13// delete pointersExample 3 (Comprehensive)
12512
3class I_Printable4{5 friend std::ostream& operator<<(std::ostream &os, const I_Printable &obj);6public:7 virtual void print(std::ostream &os) const = 0;8};9
10// all the classes we are going to define in this example are I_Printable's derived11// class so by using the base class reference 'obj' we will be able to use dynamic12// polymorphism to match the appropirate one13std::ostream& operator<<(std::ostream &os, const I_Printable &obj)14{15 obj.print(os);16 return os;17}18
19class Account : public I_Printable20{21public:22 virtual void withdraw(double amount)23 {24 std::cout << "Account::withdraw()" << std::endl;25 }26 virtual void print(std::ostream &os) const override27 {28 os << "Account display";29 }30 virtual ~Account() {}31};32
33class Checking : public Account34{35public:36 virtual void withdraw(double amount)37 {38 std::cout << "Checking::withdraw()" << std::endl;39 }40 virtual void print(std::ostream &os) const override41 {42 os << "Checking display";43 }44 virtual ~Checking() {}45};46
47class Savings : public Account48{49public:50 virtual void withdraw(double amount)51 {52 std::cout << "Savings::withdraw()" << std::endl;53 }54 virtual void print(std::ostream &os) const override55 {56 os << "Savings display";57 }58 virtual ~Savings() {}59};60
61class Trust : public Account62{63public:64 virtual void withdraw(double amount)65 {66 std::cout << "Trust::withdraw()" << std::endl;67 }68 virtual void print(std::ostream &os) const override69 {70 os << "Trust display";71 }72 virtual ~Trust() {}73};74
75class Dog : public I_Printable76{77public:78 virtual void print(std::ostream &os) const override79 {80 os << "Woof woof";81 }82 virtual ~Dog() {}83};84
85// this function will take any derived class objects of I_Printable 86// (e.g., Account objects, Savings objects, Dog objects, etc.)87void print(const I_Printable &obj)88{89 std::cout << obj << std::endl;90}91
92int main(int argc, char *argv[])93{94 // static binding95
96 Account a;97 std::cout << a << std::endl; // Account display98
99 Checking c;100 std::cout << c << std::endl; // Checking display101
102 Savings s;103 std::cout << s << std::endl; // Savings display104
105 Trust t;106 std::cout << t << std::endl; // Trust display107
108 Dog d;109 std::cout << d << std::endl; // Woof woof110 111 // dynamic binding112
113 Account *p1 = new Account();114 std::cout << *p1 << std::endl; // Account display115
116 Account *p2 = new Checking();117 std::cout << *p2 << std::endl; // Checking display118
119 Dog *p3 = new Dog();120 std::cout << *p3 << std::endl; // Woof woof121
122 print(*p3); // Woof woof123
124 return 0;125 }91Account display2Checking display3Savings display4Trust display5Woof woof6Account display7Checking display8Woof woof9Woof woof
Example
2412
3class Base4{5public:6 virtual void func1() { std::cout << "func1()" << std::endl; }7 virtual void func2() { std::cout << "func2()" << std::endl; }8};9
10class Derived : public Base11{12public:13 virtual void func1() { std::cout << "Derived::func1" << std::endl; }14 void func3() { std::cout << "func3" << std::endl; }15};16
17int main(int argc, char *argv[])18{19 Base *p1 = new Base();20 p1->func1();21
22 Base *p2 = new Derived();23 p2->func1();24}21func1()2Derived::func1

Polymorphism helps make your code simpler, more scalable, more general, etc.
Gives you ability to access to all the derived classes and their features through the base class pointer or reference.
If you did not have polymorphism, and you have a container that contains the derived classes you to do something with:
You would go through the iterator and grab every object out of the iterator, then you would have to ask the object "what are you?" and then you would down cast that to the pointer of that object and then execute that behavior of that class object.
siwtch-case statement would be a good construct to use to implement this
behavior without polymorphism
With polymorphism, just by accessing the derived objects through the base class pointer or reference you will be able to execute those objects' specific behaviors.
Mitropoulos, F. (2022). Beginning C++ Programming - From Beginner to Beyond [Video file]. Retrieved from https://www.udemy.com/course/beginning-c-plus-plus-programming/