C++ Polymorphism with Example

โšก Smart Summary

Polymorphism in C++ allows a single member function to take many forms, behaving differently depending on the object that invokes it. It builds on inheritance and appears in two flavors, resolved at either compile time or runtime.

  • ๐Ÿ”ท Definition: Polymorphism means “many forms,” letting one member function behave differently based on the calling object.
  • ๐Ÿงญ Two types: C++ provides compile-time (static) polymorphism and runtime (dynamic) polymorphism.
  • ๐Ÿงฉ Compile time: Function overloading and operator overloading select the correct function during compilation.
  • ๐Ÿ” Run time: Function overriding and virtual functions resolve the call while the program executes.
  • โš™๏ธ Virtual functions: The virtual keyword in a base class enables late binding through a base-class pointer.
  • ๐Ÿค– AI assistance: GitHub Copilot and similar assistants scaffold overloaded, overridden, and virtual functions from a short comment.

C++ Polymorphism

What is Polymorphism in C++?

In C++, polymorphism causes a member function to behave differently based on the object that calls/invokes it. Polymorphism is a Greek word that means to have many forms. It occurs when you have a hierarchy of classes related through inheritance.

For example, suppose we have the function makeSound(). When a cat calls this function, it will produce the meow sound. When a cow invokes the same function, it will provide the moow sound.

Polymorphism in C++ makeSound example

Though we have one function, it behaves differently under different circumstances. The function has many forms; hence, we have achieved polymorphism.

With the core idea in place, C++ organizes this behavior into two distinct categories.

Types of Polymorphism

C++ supports two types of polymorphism:

  • Compile-time polymorphism, and
  • Runtime polymorphism.

Types of polymorphism in C++

The first category resolves every function call before the program ever runs.

Compile Time Polymorphism

You invoke the overloaded functions by matching the number and type of arguments. The information is present during compile-time. This means the C++ compiler will select the right function at compile time.

Compile-time polymorphism is achieved through function overloading and operator overloading.

Function Overloading

Function overloading occurs when we have many functions with similar names but different arguments. The arguments may differ in terms of number or type.

Example 1

#include <iostream> 
using namespace std;

void test(int i) {
	cout << " The int is " << i << endl;
}
void test(double  f) {
	cout << " The float is " << f << endl;
}
void test(char const *ch) {
	cout << " The char* is " << ch << endl;
}

int main() {
	test(5);
	test(5.5);
	test("five");
	return 0;
}

Output:

C++ function overloading Example 1 output

Here is a screenshot of the code:

C++ function overloading Example 1 code

Code Explanation:

  1. Include the iostream header file into our code. We will be able to use its functions.
  2. Include the std namespace in our code. We will be able to use its classes without calling it.
  3. Create a function named test that takes an integer parameter i. The { marks the beginning of the body of function test.
  4. Statement to be executed if the above function test is invoked/called.
  5. End of the body of above function test.
  6. Create a function named test that takes a float parameter f. The { marks the beginning of the body of function test.
  7. Statement to be executed if the above function test is invoked/called.
  8. End of the body of the above function test.
  9. Create a function named test that takes a character parameter ch. The { marks the beginning of the body of function test.
  10. Statement to be executed if the above function test is invoked/called.
  11. End of the body of the above function test.
  12. Call the main() function. The { marks the beginning of the body of the function.
  13. Call the function test and passing 5 to it as the value of the argument. This invokes the test function that accepts an integer argument, that is, the first test function.
  14. Call the function test and passing 5.5 to it as the value of the argument. This will invoke the test function that accepts a float argument, that is, the second test function.
  15. Call the function test and passing five to it as the value of the argument. This will invoke the test function that accepts a character argument, that is, the third test function.
  16. The program must return a value if it runs successfully.
  17. The end of the body of the main() function.

We have three functions with the same name but different types of arguments. We have achieved polymorphism.

Operator Overloading

In Operator Overloading, we define a new meaning for a C++ operator. It also changes how the operator works. For example, we can define the + operator to concatenate two strings. We know it as the addition operator for adding numerical values. After our definition, when placed between integers, it will add them. When placed between strings, it will concatenate them.

Example 2

#include<iostream> 
using namespace std;

class ComplexNum {
private:
	int real, over;
public:
	ComplexNum(int rl = 0, int ov = 0) {
		real = rl;   
		over = ov; 
	}

	ComplexNum operator + (ComplexNum const &obj) {
		ComplexNum result;
		result.real = real + obj.real;
		result.over = over + obj.over;
		return result;
	}
	void print() { 
		cout << real << " + i" << over << endl; 
	}
};
int main()
{
	ComplexNum c1(10, 2), c2(3, 7);
	ComplexNum c3 = c1+c2;
	c3.print();
}

Output:

C++ operator overloading Example 2 output

Here is a screenshot of the code:

C++ operator overloading Example 2 code

C++ operator overloading Example 2 result

Code Explanation:

  1. Include the iostream header file into our program in order to use its functions.
  2. Include the std namespace into our program in order to use its classes without calling it.
  3. Create a class named ComplexNum. The { marks the beginning of the class body.
  4. Use the private access modifier to mark variables as private, meaning they can only be accessed from within the class.
  5. Define two integer variables, real and over.
  6. Use the public access modifier to mark the constructor as public, meaning that it will be accessible even from outside the class.
  7. Create the class constructor and initializing the variables.
  8. Initialize the value of the variable real.
  9. Initialize the value of the variable over.
  10. End of the constructor body.
  11. We need to override the meaning of the + operator.
  12. Create the data type result of type ComplexNum.
  13. Use the + operator with complex numbers. This line will add the real part of a number to the real part of another number.
  14. Use the + operator with complex numbers. This line will add the imaginary part of a number to the imaginary part of another number.
  15. The program will return the value of the variable result upon successful execution.
  16. End of the definition of the new meaning of + operator, that is, overloading.
  17. Call the print() method.
  18. Print the new complex number after addition on the console.
  19. End of the body of print() function.
  20. End of the body of ComplexNum class.
  21. Call the main() function.
  22. Pass the values of both real and complex parts to be added. The first part of c1 will be added to the first part of c2, that is, 10+3. The second part of c1 will be added to the second part of c, that is, 2+7.
  23. Perform an operation using the overloaded + operator and storing the result in variable c3.
  24. Print the value of variable c3 on the console.
  25. End of the body of main() function.

Where compile-time polymorphism decides early, the second category defers the choice until execution.

Runtime Polymorphism

This happens when an object’s method is invoked/called during runtime rather than during compile time. Runtime polymorphism is achieved through function overriding. The function to be called/invoked is established during runtime.

Function Overriding

Function overriding occurs when a function of the base class is given a new definition in a derived class. At that time, we can say the base function has been overridden.

For example:

#include <iostream>
using namespace std;
class Mammal {

public:
	void eat() {

		cout << "Mammals eat...";
	}

};

class Cow: public Mammal {

public:
	void eat() {

		cout << "Cows eat grass...";
	}
};
int main(void) {

	Cow c = Cow();

	c.eat();

	return 0;

}

Output:

C++ function overriding output

Here is a screenshot of the code:

C++ function overriding code

Code Explanation:

  1. Import the iostream header file into our program to use its functions.
  2. Include the std namespace into our program in order to use its classes without calling it.
  3. Create a class named Mammal. The { marks the beginning of the class body.
  4. Use the public access modifier to set the function we are about to create as publicly accessible. It will be accessible from outside this class.
  5. Create a public function named eat. The { marks the beginning of the function body.
  6. Print the statement added to the cout function when the function eat() is invoked.
  7. The end of the body of function eat().
  8. End of the body of the class Mammal.
  9. Create a class named Cow that inherits the Mammal class. Cow is the derived class, while Mammal is the base class. The { marks the beginning of this class.
  10. Use the public access modifier to mark the function we are about to create as publicly accessible. It will be accessible from outside this class.
  11. Override the function eat() that was defined in the base class. The { marks the beginning of the function body.
  12. The statement to print on the console when this function is invoked.
  13. End of the body of the function eat().
  14. End of the body of the class Cow.
  15. Call the main() function. The { marks the beginning of the body of this function.
  16. Create an instance of the Cow class and giving it the name c.
  17. Call the eat() function defined in the Cow class.
  18. The program must return a value upon successful completion.
  19. End of the main() function.

C++ Virtual Function

A virtual function is another way of implementing run-time polymorphism in C++. It is a special function defined in a base class and redefined in the derived class. To declare a virtual function, you should use the virtual keyword. The keyword should precede the declaration of the function in the base class.

If a virtual function class is inherited, the virtual class redefines the virtual function to suit its needs. For example:

#include <iostream>  
using namespace std;
class ClassA {
		public:
		virtual void show() {
			cout << "The show() function in base class invoked..." << endl;
		}
	};
	class ClassB :public ClassA {
	public:
		void show() 	{
			cout << "The show() function in derived class invoked...";
		}
	};
	int main() {
		ClassA* a;   
		ClassB b;
		a = &b;
		a->show();      
	}

Output:

C++ virtual function output

Here is a screenshot of the code:

C++ virtual function code

Code Explanation:

  1. Include the iostream header file in the code to use its functions.
  2. Include the std namespace in our code to use its classes without calling it.
  3. Create a class named ClassA.
  4. Use the public access modifier to mark a class member as publicly accessible.
  5. Create a virtual function named show(). It will be a public function.
  6. The text to print when the show() invoked is invoked. The endl is a C++ keyword, which means end line. It moves the mouse cursor to the next line.
  7. End of the body of the virtual function show().
  8. End of the body of the class ClassA.
  9. Creating a new class named ClassB that inherits the class ClassA. ClassA becomes the base class while ClassB becomes the derived class.
  10. Use the public access modifier to mark a class member as publicly accessible.
  11. Redefine the virtual function show() derived in the base class.
  12. The text to print on the console when the show() function defined in the derived class is invoked.
  13. End of the body of the show() function.
  14. End of the body of the derived class, ClassB.
  15. Call the main() function. The program logic should be added within its body.
  16. Create a pointer variable named a. It points to the class named ClassA.
  17. Create an instance of the class named ClassB. The instance is given the name b.
  18. Assign the values stores in the address b in the variable a.
  19. Invoke the show() function defined in the derived class. Late binding has been implemented.
  20. End of the body of the main() function.

With both mechanisms covered, the table below contrasts them side by side.

Compile-Time Polymorphism Vs. Run-Time Polymorphism

Here are the major differences between the two:

Compile-time polymorphism Run-time polymorphism
It is also called early binding or static polymorphism It is also called late/dynamic binding or dynamic polymorphism
The method is called/invoked during compile time The method is called/invoked during run time
Implemented via function overloading and operator overloading Implemented via method overriding and virtual functions
Example, method overloading. Many methods may have similar names but different number or types of arguments Example, method overriding. Many methods may have a similar name and the same prototype.
Faster execution since the method discovery is done during compile time Slower execution since method discovery is done during runtime.
Less flexibility for problem-solving is provided since everything is known during compile time. Much flexibility is provided for solving complex problems since methods are discovered during runtime.

FAQs

Overloading defines several functions with the same name but different parameters, resolved at compile time without inheritance. Overriding redefines an inherited base-class function with the same signature in a derived class, resolved at runtime when the function is declared virtual.

A pure virtual function is declared with = 0 and has no body in the base class. Any class containing one becomes an abstract class, which cannot be instantiated directly and forces derived classes to supply their own implementation.

When a class declares virtual functions, the compiler builds a virtual table (vtable) of function pointers. Each object stores a hidden pointer to its class vtable, so a base-class pointer calls the correct overridden function at runtime through late binding.

Yes. Templates let one function or class operate on many data types, with the compiler generating type-specific code during compilation. This generic programming is a form of static polymorphism that complements function overloading and operator overloading.

When you delete a derived object through a base-class pointer, only a virtual destructor guarantees the derived destructor also runs. Without it, cleanup is incomplete and causes memory leaks. Declaring the base destructor virtual makes destruction behave polymorphically.

Polymorphism lets one interface handle many object types, reducing duplicated code and simplifying maintenance. It supports flexible, extensible designs where new derived classes plug in without changing existing code, and it enables powerful patterns built on base-class pointers and references.

Yes. AI coding assistants turn a short comment into overloaded functions, virtual base classes, and derived overrides. They handle repetitive boilerplate quickly, though you should still verify virtual keywords, function signatures, and destructor safety before compiling the generated code.

Yes. GitHub Copilot suggests virtual function declarations, derived-class overrides, and operator overloads as you type. It accelerates boilerplate for inheritance hierarchies, though you should confirm the virtual keyword, matching signatures, and correct late-binding behavior before building.

Summarize this post with: