Polymorphism in C++

Polymorphism in C++

Polymorphism basically means having multiple existent forms in the program, A simple code may behave differently in different situations. For example, we have only one identity, to some we are friends, or father, student, employee etc.

Polymorphism in C++

How Polymorphism works in C++

In C++, polymorphism, generally happens because of classes objects and events are related with inheritance and hierarchy. Lets see what’s polymorphism in detail below –

Polymorphism is of two types –

  • Compile time polymorphism (demonstrates static/early binding)
    • Function Overloading
    • Operator Overloading
  • Run time polymorphism (demonstrates dynamic/late binding)
    • Function Overriding (Achieved with help of virtual keyword)

Compile Time Polymorphism

Function Overloading

This happens when in a given program there are multiple functions with same name but different arguments inside it or different parameters.

  • For example
myFunction(int a, int b) and myFunction(double a, double b).

There are two ways of overloading a function –

  • Changing number of arguments like myFunction(int a) and myFunction(int a, int b)
  • Changing type of arguments.

Example –

#include <iostream>
using namespace std;

class myClass {
  public:
  void myFunction(int a)
  {
    cout << "Accepted integer value " << a << endl;
  }
  void myFunction(double a) {
    cout << "Accepted double value " << a << endl;
  }
  void myFunction(int a, int b) 
  {
    cout << "Accepted multiple values, Value1:" << a << endl;
    cout << "Accepted multiple values, Value2:" << b << endl;
  }
};

int main() {
  myClass myclassObj;

  // will go to function with int as passed on variable as value passed is int type
  myclassObj.myFunction(5);

  // will go to function with double 
  myclassObj.myFunction(500.263);

  // will go to function with multiple parameters
  myclassObj.myFunction(5,3);

return 0;
}

Output –

Accepted integer value 5
Accepted double value 500.263
Accepted multiple values, Value1: 5
Accepted multiple values, Value2: 3

Now, in the above, the functions go to their correct argument type in myClass this is called function overloading

  • Functions with same name but different return type. Example void myFunction(int a), int myFunction(int a) will throw error.
  • Pointer * vs Array int fun(int *ptr); || int fun(int ptr[]); // redeclaration of fun(int *ptr) can’t be overloaded.

Operator Overloading

Operator overloading is possible in C++, you can change the behaviour of an operator like +, -, & etc to do different things.

  • For example, you can use + to concatenate two strings.
  • Consider two strings s1 and s2, we can concatenate two strings s1 and s2 by overloading + operator as String s, s = s1 + s2; 

For Example – We know that ++x increments the value of x by 1. What if we want to overload this and increase the value by 100. The program below, does that.

#include <iostream>
using namespace std;

class Test
{
  private:
    int count;

  public:
    Test(): count(101)
   { 
     //writing fuction name followed by data member and passed on value works like constructor
   }

  // we write operator before overloading such operators
  void operator ++() 
  { 
    count = count+100; 
  }
  void Display() 
  { 
    cout<<"Count: "<<count; 
  }
};

int main()
{
  Test t;
  // this calls "function void operator ++()" function
  ++t; 
  t.Display();
  return 0;
}

Output – 201

Operators that can be overloaded –

+*/%^
&|~!,=
<><=>=++
<<>>==!=&&||
+=-=/=%=^=&=
|=*=<<=>>=[]()
->->*newnew []deletedelete []

Operators that can’t be overloaded –

::.*.?:

RunTime Polymorphism

There are two ways run time polymorphism may be achieved in C++

  • Function Overriding
  • Virtual Functions (Solves problem of static resolution while working with pointers)

Note – This information is given wrong on Gks4Gks and tut point they have explained virtual functions instead at first.

Function Overriding

In a case when a function is declared in both the parent class(Base Class) and child class(Derived Class).

  • To decide upon which function has to be called, the program will only know at the run time.
  • Let us look with very simple program below to understand the basic
#include <iostream>
using namespace std;

// This is Parent class
class Parent
{
  public:
    void print()
    {
      cout << "Parent Class printing" << endl;
    }
};

// This is Child class
class Child : public Parent
{
  public:

  // as we see that it is already declared in the parent function
  void print()
  {
    cout << "Child Class printing" << endl;
  }

};

int main()
{
  //Creating object for parent class
  Parent parent_object;

  //Creating object for child class
  //No need to get conused here its same as Child object2;
  //We are just called a default constructor
  Child child_object = Child();

  // This will go to the parent member function
  parent_object.print();

  // This will however, go to child member function
  // overrides the parent function as the object is of the child class
  child_object.print();
  return 0;
}
Output
 
Parent Class printing
Child Class printing

What exactly happens at the backend?

Even though, the child class has inherited all the functions of the parent class and has same function definition within itself. The call is made to function of the child class as the object is of the child class. This is behaviour shown by child class object is called function overriding and this happens at run time. The child object has overridden the function definition of parent class and chose to exhibit its own properties.
 
Now, what if the child class didn’t have its own function declaration, in this case it would have gone ahead with the inherited parent class definition of print() function.
 
Now, the above program was very simple right? You were probably expected some twist. Let’s look at one more example below –

Working with Pointers

  • What if we create a pointer to the parent class
  • And assign it to address of base class object

something such as this –

Parent *parent_object;

Child child_object;

parent_object = &child_object;

Now, we will expect the function called by parent_object to call the child object function right? Because the final address is pointed toward child_object location. Let us see what happens.

#include <iostream>
using namespace std;

// This is Parent class
class Parent
{
  public:
    void print()
    {
      cout << "Parent Class printing" << endl;
    }
};

// This is Child class
class Child : public Parent
{
  public:

  // as we see that it is already declared in the parent function
  void print()
  {
    cout << "Child Class printing" << endl;
  }

};

int main()
{
  Parent *parent_object; 
  Child child_object;
  parent_object = &child_object;

  // catch of the program is here
  // also as we are dealing with pointers instead of . we need to use ->
  parent_object->print();

  return 0;
}

Output – Parent Class printing

Static Resolution

Now, this happens because of how C++ was written by its writer. Now, while run time polymorphism may exhibit dynamic (or late) binding. But, this instance is a classic example of early binding as the print() function is set during the compilation of the program. The definition of C++ is written, forces this static call and is also called as static resolution.

In the above program instead of using pointers we can also simply do the same thing by writing-

Parent parent_object = Child();

Since the object is no more of object pointer type, we need to also change the function call to –

parent_object.print();

The issue caused by static resolution is solved by using virtual function that turns the early binding into late binding i.e. compile time process to runtime process.

Finally we suggest that you learn Virtual Functions after this.