CS Engineering Gyan

Polymorphism in C++

In the chapter on Object Oriented Programming, we briefly touched on polymorphism using a simple example where the same function name behaved differently depending on the type of argument passed to it. That short example was just a preview of a much bigger and more powerful concept that plays a major role in how flexible and extensible C++ programs can become.

The word polymorphism comes from a combination of two ideas meaning "many forms." In programming, it refers to the ability of a single function name, operator, or object interface to behave differently depending on the context in which it is used. Instead of writing separate, uniquely named functions for every slightly different situation, polymorphism allows the same name to adapt intelligently based on what it is working with.

C++ supports two broad categories of polymorphism: compile-time polymorphism, where the decision about which function to run is made while the program is being compiled, and runtime polymorphism, where that decision is made while the program is actually running. In this tutorial, you will learn about both categories in detail, along with the specific C++ features that make each of them possible.


Compile-Time Polymorphism

Compile-time polymorphism, also called static or early binding, happens when the compiler is able to determine exactly which version of a function or operator should run purely by looking at the code, without needing to wait until the program is executing. This is achieved in C++ mainly through function overloading and operator overloading.

Function Overloading

Function overloading allows multiple functions to share the exact same name, as long as they differ in the number or types of parameters they accept. The compiler examines the arguments used in each function call and automatically selects the matching version.

Example

#include <iostream>

using namespace std;

class Stats {

    public:

        void showViews(int views) {

            cout << "Total views: " << views << endl;

        }

        void showViews(int views, int likes) {

            cout << "Total views: " << views << ", Total likes: " << likes << endl;

        }

};

int main() {

    string channel = "CS Engineering Gyan";

    Stats videoStats;

    cout << channel << " stats update:" << endl;

    videoStats.showViews(4200);

    videoStats.showViews(4200, 380);

    return 0;

}

Output

CS Engineering Gyan stats update:

Total views: 4200

Total views: 4200, Total likes: 380

Here, both versions of showViews share the same name, but the compiler chooses the correct one to run based purely on how many arguments are supplied in each individual function call.


Operator Overloading

Operator overloading allows existing C++ operators, such as plus or equals, to be redefined so that they work meaningfully with objects of a user-defined class, in addition to their usual behavior with built-in types like integers and floats.

Example

#include <iostream>

using namespace std;

class WatchTime {

    public:

        int minutes;

        WatchTime(int m) {

            minutes = m;

        }

        WatchTime operator+(WatchTime const& other) {

            WatchTime result(minutes + other.minutes);

            return result;

        }

};

int main() {

    string channel = "CS Engineering Gyan";

    WatchTime video1(15);

    WatchTime video2(22);

    WatchTime combined = video1 + video2;

    cout << channel << " combined watch time: " << combined.minutes << " minutes" << endl;

    return 0;

}

Output

CS Engineering Gyan combined watch time: 37 minutes

Here, the plus operator has been redefined inside the WatchTime class, allowing two WatchTime objects to be added together directly using the familiar plus symbol, even though the compiler would not know how to do this automatically on its own.


Runtime Polymorphism

Runtime polymorphism, also called dynamic or late binding, happens when the decision about which function to actually execute is made while the program is running, rather than at compile time. This is achieved in C++ using virtual functions, combined with inheritance and pointers or references to base class objects.

This form of polymorphism is especially useful when working with a collection of related objects through a common base class pointer, where each object might need to respond differently to the exact same function call.


Function Overriding

Function overriding occurs when a derived class defines a function with the exact same name, return type, and parameters as a function already present in its base class. The derived class's version effectively replaces the base class's version when called through that derived object.

Example

#include <iostream>

using namespace std;

class Content {

    public:

        void publish() {

            cout << "Publishing generic content" << endl;

        }

};

class Video : public Content {

    public:

        void publish() {

            cout << "Publishing a video on CS Engineering Gyan" << endl;

        }

};

int main() {

    Video newUpload;

    newUpload.publish();

    return 0;

}

Output

Publishing a video on CS Engineering Gyan

Here, calling publish on a Video object runs the version defined inside Video itself, rather than the one defined in Content, since the derived class's function overrides the base class's function of the same signature.


Virtual Functions

Simply overriding a function is not always enough to guarantee correct behavior, especially when working through base class pointers. A virtual function is a special member function in the base class, marked with the virtual keyword, which tells the compiler to decide which version to run based on the actual object being pointed to, rather than the type of the pointer itself.

Example

#include <iostream>

using namespace std;

class Content {

    public:

        virtual void publish() {

            cout << "Publishing generic content" << endl;

        }

};

class Video : public Content {

    public:

        void publish() override {

            cout << "Publishing a video on CS Engineering Gyan" << endl;

        }

};

class LiveStream : public Content {

    public:

        void publish() override {

            cout << "Starting a live stream on CS Engineering Gyan" << endl;

        }

};

int main() {

    Content* contentPtr;

    Video sampleVideo;

    LiveStream sampleStream;

    contentPtr = &sampleVideo;

    contentPtr->publish();

    contentPtr = &sampleStream;

    contentPtr->publish();

    return 0;

}

Output

Publishing a video on CS Engineering Gyan

Starting a live stream on CS Engineering Gyan

Even though contentPtr is declared as a pointer to the base class Content, it correctly calls the specific publish function belonging to whichever object it actually points to at that moment. This is the real strength of runtime polymorphism, since the correct behavior is chosen dynamically rather than being fixed at compile time.

Without the virtual keyword on the base class function, the program would always call the base class version regardless of the actual object type, which defeats the purpose of overriding entirely.


Pure Virtual Functions and Abstract Classes

A pure virtual function is a virtual function that has no actual implementation inside the base class, and is instead required to be defined by every derived class. A class containing at least one pure virtual function becomes what is known as an abstract class, meaning it can no longer be used to create objects directly on its own.

Example

#include <iostream>

using namespace std;

class Content {

    public:

        virtual void publish() = 0;

};

class Video : public Content {

    public:

        void publish() override {

            cout << "Publishing a video on CS Engineering Gyan" << endl;

        }

};

int main() {

    Video newUpload;

    newUpload.publish();

    return 0;

}

Output

Publishing a video on CS Engineering Gyan

The = 0 at the end of the publish function declaration marks it as a pure virtual function, making Content an abstract class. Any class that inherits from Content, such as Video, is now required to provide its own implementation of publish, or it too will remain abstract and unable to create objects.

Abstract classes are extremely useful for defining a common interface that multiple related classes must follow, without dictating exactly how each one accomplishes that behavior internally.


Compile-Time vs Runtime Polymorphism

Compile-Time Polymorphism Runtime Polymorphism
Decision about which function to call is made during compilation. Decision about which function to call is made while the program runs.
Achieved through function overloading and operator overloading. Achieved through virtual functions and function overriding.
Generally offers faster execution, since no runtime decision is needed. Offers greater flexibility, especially when working with base class pointers.

Advantages and Limitations of Polymorphism

Advantages Limitations
Allows the same function name to work sensibly across different situations. Runtime polymorphism can introduce a small performance overhead compared to direct function calls.
Makes it possible to write flexible code that works with a whole family of related classes. Overriding a function incorrectly, such as mismatched parameters, can create confusing bugs.
Encourages a clean, consistent interface across multiple derived classes. Beginners can find the distinction between overloading and overriding confusing at first.

Best Practices While Using Polymorphism


Common Mistakes Beginners Make

Mistake Correct Practice
Confusing function overloading with function overriding. Remember that overloading involves different parameter lists in the same class, while overriding involves the same signature in a derived class.
Forgetting to mark a base class function as virtual before overriding it. Always mark base class functions as virtual when derived classes are meant to override them.
Trying to create an object of an abstract class directly. Remember that a class with a pure virtual function cannot be instantiated on its own.
Assuming a non-virtual base class function will behave polymorphically through a pointer. Only virtual functions support true runtime polymorphism through base class pointers.

Frequently Asked Interview Questions

  1. What is polymorphism in C++?
    Polymorphism is the ability of a function, operator, or object interface to behave differently depending on the context in which it is used.
  2. What is the difference between compile-time and runtime polymorphism?
    Compile-time polymorphism is resolved while compiling the code, while runtime polymorphism is resolved while the program is actually running.
  3. What is function overloading?
    Function overloading is having multiple functions with the same name but different parameter lists within the same class.
  4. What is function overriding?
    Function overriding is when a derived class defines a function with the exact same signature as one in its base class, replacing its behavior.
  5. What is a virtual function?
    A virtual function is a base class function marked with the virtual keyword, allowing the correct overridden version to be selected at runtime.
  6. What is a pure virtual function?
    A pure virtual function is a virtual function with no implementation in the base class, which every derived class must define on its own.
  7. What is an abstract class?
    An abstract class is a class that contains at least one pure virtual function and therefore cannot be used to create objects directly.
  8. Why is the override keyword used in C++?
    The override keyword helps the compiler catch mistakes where a derived class function does not actually match the base class function it was meant to override.

Summary

Polymorphism allows the same function name, operator, or interface to adapt its behavior depending on how it is used, making C++ programs more flexible and easier to extend. Compile-time polymorphism, achieved through function overloading and operator overloading, lets the compiler resolve behavior directly from the code itself.

Runtime polymorphism, achieved through virtual functions and function overriding, allows a program to decide which version of a function to run while it is actually executing, which becomes especially powerful when working with collections of related objects through a common base class pointer. Pure virtual functions and abstract classes take this idea further, allowing a base class to define a required interface without dictating exactly how each derived class must implement it.

With polymorphism covered, you now have a solid understanding of all four pillars of Object Oriented Programming: encapsulation, abstraction, inheritance, and polymorphism. From here, you are well prepared to move on to file handling in C++, where these object-oriented concepts often come together with real, practical program logic.


← Previous: Inheritance in C++ Next: File Handling in C++ →

Home Visit Our YouTube Channel