Polymorphism in C++

Polymorphism is about a same piece of code behaves differently in different situations. In C++, polymorphism is achieved by function overloading, operator overloading, and virtual function overriding. Same interface, function or operation, can be bound to different object types, hence, different behaviors. Binding can happen compile time or runtime. Base on that polymorphism is categorized as 1) compile time and 2) runtime.

Compile Time Polymorphism

In this case, the compiler can bind the interface with the object in time compilation. That means the behavior is defined before the program actually runs. This type of binding is also known as early binding or static binding. Function or operator overloading based polymorphisms are of this type.

Polymorphism Based on Function Overloading

C++ allows to have multiple functions with the same name but different number or type of parameters. This is called function overloading.

#include <iostream>
using namespace std;

int add (int a, int b) {
    return (a+b);
}

string add (string a, string b) {
    return a.append(b);
}

int main() {
    int a = 100;
    int b = 200;
    int r;
    r = add(a, b);
    cout << r << endl;

    string x = "Hello ";
    string y = "world";
    string z;
    z = add(x, y);
    cout << z << endl;
    
    return 0;
}
$ g++ -o test test.cpp 
$ ./test 
300
Hello world

Notice the following two lines from the example above.

r = add(a, b);
z = add(x, y);

Very similar looking lines – same function calls. But the first function call adds two input parameters and the second one concatenated two input parameters. Same function call but two different behaviors.

Polymorphism Based on Operator Overloading

We are already familiar with some standard overloaded operators in C++. Let’s take an example.

#include <iostream>
using namespace std;

int main() {
    int a = 100;
    int b = 200;
    int r;
    r = a + b;
    cout << r << endl;

    string x = "Hello ";
    string y = "world";
    string z;
    z = x + y;
    cout << z << endl;
    
    return 0;
}
$ ./test 
300
Hello world

Now consider these two lines:

r = a + b;
z = x + y;

Here also, the exact same operator, ‘+‘, did two different things. The first ‘+‘ operator did simple arithmetic summation on two numbers. But the second one concatenated two strings.

Here the ‘+‘ operator is overloaded inside the “std::string” class that defines what to do on string type operands.

We can define our own overloaded operators also.

#include <iostream>
using namespace std;

class Complex {
public:
    Complex(int r, int i) {
        real_ = r;
        img_ = i;
    }

    Complex operator+(Complex const& c) {
        Complex r(0, 0);
        r.real_ = real_ + c.real_;
        r.img_ = img_ + c.img_;

        return r;
    }

    void print() {
        cout << real_ << " + i" << img_ << endl;
    }

private:
    int real_;
    int img_;
};

int main() {
    Complex c1(10, 20);
    Complex c2(30, 40);

    cout << "c1 = ";
    c1.print();
    cout << "c2 = ";
    c2.print();
    
    Complex r = c1 + c2;

    cout << "r =  ";
    r.print();

    return 0;
}
$ ./test 
c1 = 10 + i20
c2 = 30 + i40
r =  40 + i60

Here we overloaded the ‘+‘ operator for our Complex class and defined how to add to complex numbers.

In all the examples so far, the compiler can decide exactly what function to call or how to use the operator based on the input parameter type or the operator type.

Run Time Polymorphism

There are other type of polymorphism where the compiler will have no way to know which function to call until the program runs. Virtual function based polymorphism is one such type.

#include <iostream>
using namespace std;


class Animal{
public:
    virtual void speak() = 0;
};

class Cat : public Animal {
public:
    virtual void speak() {
        cout << "Meow..." << endl;
    }
};

class Dog : public Animal {
public:
    virtual void speak() {
        cout << "Bark..." << endl;
    }
};

int main() {
    Animal *arr[2];

    int input = 0;
    for (int i = 0; i < 2; i++) {
        cout << "Cat [0] or Dog [1]: ";
        cin >> input;

        if (input == 0) {
            arr[i] = new Cat();
        } else {
            arr[i] = new Dog();
        }
    }

    for (int i = 0; i < 2; i++) {
        arr[i]->speak();
    }

    // cleanup
    for (int i = 0; i < 2; i++) {
        delete arr[i];
    }

    return 0;
}
$ ./test 
Cat [0] or Dog [1]: 1
Cat [0] or Dog [1]: 0
Bark...
Meow...

The speak() function is declared as ‘virtual‘ in the Animal class. The Cat class overrode the function and printed ‘Bark…‘. Similarly, the Dog class also overrode and printed ‘Meow…‘ from the speak() function.

Now consider this line:

arr[i]->speak();

This line is called twice in a loop. First it printed ‘Bark…‘ and then ‘Meow…‘ because I entered ‘Dog‘ first and then ‘Cat‘. The same statement behaved differently based on my input. This is another type of polymorphism. But I could have entered any combination of Dog and Cat runtime. This statement would have printed accordingly.

The compiler can not know which input combination I will proved runtime. So, it can not fix which function to call in time of compilation.

Here the ‘arr‘ can hold both Cat or Dog type objects. The array is filled up with different types of objects based on user input. The program checks the type of the array element, arr[i], and then call the appropriate function while the program is running. This mechanism is known as late binding or runtime bininding.

Author: Srikanta

I write here to help the readers learn and understand computer programing, algorithms, networking, OS concepts etc. in a simple way. I have 20 years of working experience in computer networking and industrial automation.


If you also want to contribute, click here.

Leave a Reply

Your email address will not be published. Required fields are marked *

0
0
0
0
0
0