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
xxxxxxxxxx
181// Assume that each type of account has its own 'withdraw()' function
2
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 version
18 // 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 pointerp
will be pointing to at run-time. All it knows at compile time is thatp
points to anAccount
type object. So, the compiler will go ahead and bindAccount
'swithdraw()
function when it seesp->withdraw(1000);
.
Let's see the following example:
xxxxxxxxxx
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 was
5 // declared
6}
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 theAccount
part of the passed object.There is a way for C++ to ask the
Account
object being passed in what kind of account they are, and then depending on that, we can haveif...else
statements 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.
xxxxxxxxxx
171// Assume that each type of account has its own 'withdraw()' function
2
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
virtual
functions 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 whatp
is pointing to and then that object's function will be called.
xxxxxxxxxx
171void display_account(const Account &acc)
2{
3 acc.display(); // will now always call the display function depending on the
4 // 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.
xxxxxxxxxx
631
2// smart pointer
3
4class Base
5{
6public:
7 void say_hello() const
8 {
9 std::cout << "Hello, I'm a Base class object" << std::endl;
10 }
11};
12
13class Derived: public Base
14{
15public:
16 void say_hello() const
17 {
18 std::cout << "Hello, I'm a Derived class object" << std::endl;
19 }
20};
21
22void greetings(const Base &obj) // this parameter can take
23{ // 1) Base class object
24 std::cout << "Greetings: "; // 2) any object of Base class' derived class
25 obj.say_hello(); // because derived class 'is-a' base class
26
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 therefore
29 // 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., no
36 // dynamic polymorphism associated with it), and it knows that it
37 // needs to bind this statically (compiler only knows at compile-time
38 // 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 statement
43
44 // problem occurs when using the base class pointer or reference
45
46 greetings(b); // Greetings: Hi, I'm a Base class object
47 greetings(d); // Greetings: Hi, I'm a Base class object
48
49 Base *ptr = new Derived(); // ptr can hold address of any Base object and Derived
50 // '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 to
53 // bind it statically and since ptr is of type Base*,
54 // Base::say_hello() will be bound to this statement
55
56 // smart pointer is no exception
57 std::unique_ptr<Base> ptr1 = std::make_unique<Derived>();
58 ptr1->say_hello(); // Hi, I'm a Base class object
59
60 delete ptr;
61
62 return 0;
63}
xxxxxxxxxx
61Hello, I'm a Base class object
2Hello, I'm a Derived class object
3Greetings: Hello, I'm a Base class object
4Greetings: Hello, I'm a Base class object
5Hello, I'm a Base class object
6Hello, I'm a Base class object
Let's assume that the following example uses the dynamic polymorphism:
xxxxxxxxxx
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 pointers
Now, 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:
xxxxxxxxxx
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 pointers
This 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:
xxxxxxxxxx
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 pointers
Think 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
xxxxxxxxxx
71// base class
2class Account
3{
4public:
5 virtual void withdraw(double amount) { /* code */ };
6 . . .
7};
This makes
withdraw()
avirtual
function 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
xxxxxxxxxx
71// derived class
2class Checking : public Account
3{
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.)
xxxxxxxxxx
171// base class
2class Account
3{
4public:
5 virtual void withdraw(double amount) { /* code */ };
6 virtual ~Account(); // simple solution for the potentially serious situation
7 . . .
8};
9
10// derived class
11class Checking : public Account
12{
13public:
14 virtual void withdraw(double amount) { /* code */ };
15 virtual ~Checking(); // simple solution for the potentially serious situation
16 . . .
17};
If none of these class had virtual
destructor
xxxxxxxxxx
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 of
Rule is simple!
If you've got a class and it's got a virtual
function, give it a virtual
destructor 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:
xxxxxxxxxx
201class Base
2{
3public:
4 virtual void say_hello const
5 {
6 std::cout << "Hello - I'm a Base class object" << std::endl;
7 }
8 virtual ~Base() {}
9};
10
11class Derived : public Base
12{
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.
xxxxxxxxxx
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, soDerived
redefinessay_hello
instead of overriding it!
We can easily prevent this error by using the C++11 override
specifier.
xxxxxxxxxx
191class Base
2{
3public:
4 virtual void say_hello const
5 {
6 std::cout << "Hello - I'm a Base class object" << std::endl;
7 }
8 virtual ~Base() {}
9};
10
11class Derived : public Base
12{
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
oeverride
keyword, 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
xxxxxxxxxx
91class My_Class final // My_Class cannot be derived from
2{
3 ...
4};
5
6class Derived final : public Base // Derived cannot be derived from
7{
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
xxxxxxxxxx
151class A
2{
3public:
4 virtual void do_something();
5};
6
7class B : public A
8{
9 virtual void do_something() final; // prevent further overriding
10};
11
12class C: public B
13{
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:
xxxxxxxxxx
71Account a;
2Account &ref = a;
3ref.withdraw(1000); // Account::withdraw()
4
5Trust t;
6Account &ref1 = t;
7ref1.withdraw(1000); // Trust::withdraw() <--- dynamic bound!
Also,
xxxxxxxxxx
101void do_withdraw(Account &account, double amount)
2{
3 account.withdraw(amount); // since withdraw() is a virtual function, it is bound dynamically
4}
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
xxxxxxxxxx
11virtual void function() = 0; // pure virtual function
Typically 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)
xxxxxxxxxx
11virtual void draw() = 0; // in the Shape class
The 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:
xxxxxxxxxx
11virtual void defend() = 0; // in the Player class
Examples
Following Shape
class has pure virtual functions so it is abstract class and therefore cannot directly instantiate objects.
xxxxxxxxxx
151class Shape // abstract class
2{
3private:
4 // attributes common to all shapes
5public:
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 class
15Shape *p = new Shape(); // Compiler ERROR: Can't instantiate an abstract class
Following Open_Shape
class is still an abstract class because it does not override the virtual functions declared in its base (Shape
) class.
xxxxxxxxxx
51class Open_Shape : public Shape // abstract class
2{
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.)
xxxxxxxxxx
221class Circle : public Shape // concrete class
2{
3private:
4 // attributes common to all shapes
5public:
6 virtual void draw() override // override keyword is not mandatory here
7 {
8 // code to draw a circle
9 }
10 virtual void rotate() override // override keyword is not mandatory here
11 {
12 // code to rotate a circle
13 }
14 virtual ~Circle();
15 . . .
16};
17
18. . .
19
20Shape *p = new Circle(); // OK
21p->draw(); // OK
22p->rotate(); // OK
The 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.
xxxxxxxxxx
11std::cout << any_object << std::endl;
Then, any_object
must conform to the Printable
interface.
xxxxxxxxxx
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 conforms
5 // to this interface
6public:
7 virtual void print(ostream &os) const = 0;
8 virtual ~Printable() {};
9 . . .
10};
11
12// overloaded operator function
13// - this would dynamically bind to the print() function of whatever object type was
14// passed in since we know that that object is printable and we have a reference to
15// it
16ostream& operator<<(ostream &os, const Printable &obj)
17{
18 obj.print(os);
19 return os;
20}
21
22class Any_Class : public Printable
23{
24public:
25 // must override Printable::print()
26 virtual void print(ostream &os) override
27 {
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:
xxxxxxxxxx
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.
xxxxxxxxxx
81class I_Shape
2{
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:
xxxxxxxxxx
81class Circle : public I_Shape
2{
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.
xxxxxxxxxx
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 pointers
Example 3 (Comprehensive)
xxxxxxxxxx
1251
2
3class I_Printable
4{
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 derived
11// class so by using the base class reference 'obj' we will be able to use dynamic
12// polymorphism to match the appropirate one
13std::ostream& operator<<(std::ostream &os, const I_Printable &obj)
14{
15 obj.print(os);
16 return os;
17}
18
19class Account : public I_Printable
20{
21public:
22 virtual void withdraw(double amount)
23 {
24 std::cout << "Account::withdraw()" << std::endl;
25 }
26 virtual void print(std::ostream &os) const override
27 {
28 os << "Account display";
29 }
30 virtual ~Account() {}
31};
32
33class Checking : public Account
34{
35public:
36 virtual void withdraw(double amount)
37 {
38 std::cout << "Checking::withdraw()" << std::endl;
39 }
40 virtual void print(std::ostream &os) const override
41 {
42 os << "Checking display";
43 }
44 virtual ~Checking() {}
45};
46
47class Savings : public Account
48{
49public:
50 virtual void withdraw(double amount)
51 {
52 std::cout << "Savings::withdraw()" << std::endl;
53 }
54 virtual void print(std::ostream &os) const override
55 {
56 os << "Savings display";
57 }
58 virtual ~Savings() {}
59};
60
61class Trust : public Account
62{
63public:
64 virtual void withdraw(double amount)
65 {
66 std::cout << "Trust::withdraw()" << std::endl;
67 }
68 virtual void print(std::ostream &os) const override
69 {
70 os << "Trust display";
71 }
72 virtual ~Trust() {}
73};
74
75class Dog : public I_Printable
76{
77public:
78 virtual void print(std::ostream &os) const override
79 {
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 binding
95
96 Account a;
97 std::cout << a << std::endl; // Account display
98
99 Checking c;
100 std::cout << c << std::endl; // Checking display
101
102 Savings s;
103 std::cout << s << std::endl; // Savings display
104
105 Trust t;
106 std::cout << t << std::endl; // Trust display
107
108 Dog d;
109 std::cout << d << std::endl; // Woof woof
110
111 // dynamic binding
112
113 Account *p1 = new Account();
114 std::cout << *p1 << std::endl; // Account display
115
116 Account *p2 = new Checking();
117 std::cout << *p2 << std::endl; // Checking display
118
119 Dog *p3 = new Dog();
120 std::cout << *p3 << std::endl; // Woof woof
121
122 print(*p3); // Woof woof
123
124 return 0;
125 }
xxxxxxxxxx
91Account display
2Checking display
3Savings display
4Trust display
5Woof woof
6Account display
7Checking display
8Woof woof
9Woof woof
Example
xxxxxxxxxx
241
2
3class Base
4{
5public:
6 virtual void func1() { std::cout << "func1()" << std::endl; }
7 virtual void func2() { std::cout << "func2()" << std::endl; }
8};
9
10class Derived : public Base
11{
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}
xxxxxxxxxx
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/