© Wright Studio / Shutterstock.com

In the software development realm, object-oriented programming has been the dominant paradigm for decades now. This is because programmers benefit from tools like polymorphism, that help their ideas come to life more easily. 

In this article, you will learn all about polymorphism: what it is, how it works, and its best practices. We promise you that understanding this fundamental concept will allow you to see your code in new ways and push you further along your programming journey. 

What is Polymorphism?

According to the etymology of the word, we can define polymorphism as the capacity of information to be communicated in more than one way. 

For example, you can take a chair and also categorize it as a seat, or furniture. These concepts are all correct and they all symbolize the same item. 

We can already appreciate how interesting polymorphism can be as a theoretical concept, but in the OOPs realm, we can take it to a whole other level. 

When programming, we have two categories of polymorphism: compile-time polymorphism and runtime polymorphism. Let’s dig a little deeper into each category.

Compile-Time Polymorphism  

As you can guess, compile-time polymorphism occurs during the compiling of our code and is achieved by “method overloading”. This means that we create more than one function or operator with the same name. 

If we create several functions using the same denomination, we consider them overloaded. The functions differentiate from each other because of the arguments in the function body. 

Therefore, the return we receive will vary depending on what the argument does to the values we send to the overloaded functions. This is called function overloading and we can see an example below:

#include 

using namespace std;

class Example {
    public:
        void func(int x)
        {
            cout << "value of x is " << x << endl;
        }
        void func(double x)
        {
            cout << "value of x is " << x << endl;
        }
        void func(int x, int
        {
            cout << "value of x and y is " << x << ", " << y
                << endl;
        }};
    int main()
    {
        Example obj1;
        obj1.func(7);
        obj1.func(9.132);
        obj1.func(85, 64);
        return 0; 
    }

The output will look something like this:

An example of function overloading.

©History-Computer.com

The above example shows how we can define three functions using the same name. In this case, what distinguishes them is the type and amount of parameters used. At the same time, in the driver code, we select what function to use depending on what parameter we pass on it.  

Function overloading is a very valuable polymorphism feature, especially when we need to execute and process different results in a single take. 

Always remember that for overloading to work the function name should be identical but the arguments should differ from each other.

Operator Overloading

When building programs using OOPs, we also have the operator overloading path. How does this work? It depends on what programming language we are working with. But basically, operator overloading occurs when an operator like “+” or “-” is applied to work with data types that aren’t numbers, like strings. 

In C++ operators can do special combinations using user-defined classes. For example, we can concatenate a string just by placing the “+” between each string. 

Here is an example for you to read through and understand the intuitive aspect of this tool:

#include 

using namespace std;

class Fraction {
private:
    int numerator;
    int denominator;

public:
    Fraction(int num = 0, int den = 1) : numerator(num), denominator(den) {}

    // Overloading the addition operator (+)
    Fraction operator+(const Fraction& other) {
        int num = numerator * other.denominator + other.numerator * denominator;
        int den = denominator * other.denominator;
        return Fraction(num, den);
    }

    // Overloading the subtraction operator (-)
    Fraction operator-(const Fraction& other) {
        int num = numerator * other.denominator - other.numerator * denominator;
        int den = denominator * other.denominator;
        return Fraction(num, den);
    }

    // Overloading the output stream operator (<<)
    friend ostream& operator<<(ostream& out, const Fraction& fraction) {
        out << fraction.numerator << "/" << fraction.denominator;
        return out;
    }
};

int main() {
    Fraction fraction1(1, 2);
    Fraction fraction2(3, 4);

    Fraction sum = fraction1 + fraction2;
    Fraction difference = fraction1 - fraction2;

    cout << "Fraction 1: " << fraction1 << endl;
    cout << "Fraction 2: " << fraction2 << endl;
    cout << "Sum: " << sum << endl;
    cout << "Difference: " << difference << endl;

    return 0;
}

Here, we have a “main()” function that displays operator overloading in a very tidy manner.

In the above example, “fraction” has a numerator and denominator. We can perform operations on “fraction” objects by overloading the addition and subtraction operators. We also overload the output stream operator, allowing us to print “fraction” objects directly using the “cout” stream.

So, operator overloading allows us to extend the regular use that operators have and gain access to a whole spectrum of new tools. There is a plethora of operators you can overload. Make sure to check the official C++ documentation to expand on the topic!

Operator overloading is implemented in a program.

©History-Computer.com

Runtime Polymorphism  

Runtime polymorphism, also known as dynamic polymorphism and late binding, occurs during program runtime. The Runtime state is the last step of the program’s lifecycle, from the execution until termination. 

Function overriding (do not mistake it for overloading) is the perfect example of runtime polymorphism applied in the structure of a program. Overriding takes a base class function and “recycles” it to create another class with the same return type and parameters.

#include 

using namespace std;

class base {
public:
    virtual void print() {
        cout << "print base class" << endl;
    }
    void show() {
        cout << "show base class" << endl;
    }
};

class derived : public base {
public:
    void print() {
        cout << "print derived class" << endl;
    }
    void show() {
        cout << "show derived class" << endl;
    }
};

int main() {
    base* bptr;
    derived d;
    bptr = &d;
    bptr->print();
    bptr->show();
    return 0;
}

The output will look like this:

A program to show the implementation of runtime polymorphism.

©History-Computer.com

Contrary to overloading, function overriding can only run once (because it works during Runtime) and needs inheritance to function.

Usually, the function created by overriding is called a virtual function. They are worth mentioning because it’s the result of the whole process of declaration in the base class and definition in the resulting class. 

Virtual functions work with objects, they are (and must be) dynamic, and they execute during runtime. But what role does inheritance play in all this?

Polymorphism and Inheritance: What’s the Difference?

Inheritance is another OOPs feature that extends a function’s possibilities. Like polymorphism, it also works by creating new classes from existing ones but it takes a different approach. 

As the name suggests, the new class (also known as “derived class” or “child class”) “inherits” the return type and parameter of the parent class. We can add new parameters to the child class without changing the parent class, creating a variation of it. 

Even if they sound alike, polymorphism and inheritance have some key differences that will help us decide which one is better for the job. 

When we want a customizable code with a wide variety of functions that do a lot of different tasks, we should approach the job using inheritance. We can start with a parent class that contains methods, functions, and other parameters and then start building our architecture from that.

On the other hand, polymorphism can save us a lot of time by allowing us to create a group of functions with the same name so that when they are called, they execute at the same time and return the values we need. 

Polymorphism does not give our code the complexity that class inheritance has, but instead had some customization options that we already saw. The objects can decide which function form to adopt and when (Compile-time or Runtime).

Polymorphism in C++: Fully Explained

Polymorphism might seem like a complex concept, but it’s worth dedicating time to it. We saw what it can do for our code, and when it’s best to apply it. It’s a fundamental topic if we are going to dedicate ourselves to modern programming languages.

Now we know how to apply polymorphism using function overloading and overriding, helping us to take regular functions to another level. We also learned about compile-time and runtime, and what they mean to the lifecycle of our programs. 

Additionally, we got into the specifics of inheritance and how it differs from polymorphism. 

As we always recommend, it’s a great practice to check C++’s official documentation to enrich the concepts we just learned, and also take the time to replicate and extend the code examples we just gave you.

Polymorphism In C++: Fully Explained FAQs (Frequently Asked Questions) 

Are late binding and runtime the same process?

No, they are not, but they are close. Late binding happens during Runtime by referring Virtual classes to the pointer (base class). 

Is polymorphism better than inheritance?

Polymorphism and inheritance are both features of OOP architectures. Each has its unique strengths, as we talked about in the article. It’s our job as developers to decide which one is better for the job. Just remember: inheritance helps us work with classes and child classes, and polymorphism is the right tool when we need several functions with the same name. 

What is Runtime?

In programming, runtime is one stage of the several that a program adopts during its lifecycle. The runtime stage comes after “load”; once all the instructions have been inputted by the developers. The program is ready to execute alongside the runtime environment instructions provided by the system.

Is C++ a popular programming language?

Yes, for sure. Developers use C++ for a wide variety of projects, including embedded systems, video game programming, browser infrastructure, database design, and desktop software. Polymorphism is one of the main features that people seek in C++ and other OOPs like Java or Python. 

What is Function Overloading?

Function Overloading is one of the most used features of polymorphism, and it’s what gives this OOPs tool its popularity. Overloading a function means giving two or more functions the same name, but changing the return type and other parameters inside the its body. 

About the Author

More from History-Computer

  • Scaler Available here: https://www.scaler.com/topics/cpp/polymorphism-in-cpp/
  • Geeks For Geeks Available here: https://www.geeksforgeeks.org/cpp-polymorphism/
  • W3Schools Available here: https://www.w3schools.com/cpp/cpp_polymorphism.asp
  • Great Learning Available here: https://www.mygreatlearning.com/blog/polymorphism-in-cpp/
  • Java T Point Available here: https://www.javatpoint.com/cpp-polymorphism