Module 4: Overloading, Templates & Inheritance – BEU B.Tech CSE 3rd Semester Notes
Complete Module 4 notes for BEU B.Tech CSE 3rd Semester covering Function Overloading, Operator Overloading, Templates, Inheritance, Polymorphism, Virtual Functions, Pure Virtual Functions, Abstract Class, and Virtual Base Class. Easy language, exam-focused notes for AI, ML, DS, SE, CSIT branches.
Module 4 of the Bihar Engineering University (BEU) B.Tech 3rd Semester CSE syllabus focuses on the most important and advanced concepts of Object-Oriented Programming in C++. This module covers Function Overloading, Operator Overloading, Templates, Inheritance, Polymorphism, Virtual Functions, Pure Virtual Functions, Abstract Classes, and Virtual Base Classes. These concepts help students understand how C++ supports flexibility, code reuse, and runtime decision-making through OOP features.
This module is extremely valuable for CSE students from all branches, including AI, Machine Learning, Data Science, Software Engineering, and CSIT, because these topics lay the foundation for building large-scale applications and reusable components. Understanding Module 4 improves your ability to design cleaner programs, handle dynamic behavior, manage complex class relationships, and create generic code using templates.
These Module 4 notes have been written in simple language to help BEU students learn each concept step-by-step and prepare efficiently for university exams and viva.
Inheritance
Inheritance is a feature of object-oriented programming in C++ that allows one class to reuse the data members and member functions of another class. The class that provides its members is called the base class, and the class that receives and extends those members is called the derived class. This avoids rewriting the same code in multiple classes, keeps common features in a single place, and allows new classes to be built by extending existing ones. Inheritance also helps in creating a clear hierarchy of classes and supports polymorphism, where a derived class object can be treated as a base class object in many situations.
Important Points
The class that is inherited from is called the base class, and the class that inherits is called the derived class.
Inheritance is mainly used for code reusability and to avoid duplication of common data and functions.
A derived class can use base class members (according to access rules) and can also define its own additional members.
Private members of the base class are not directly accessible in the derived class, but they can be accessed through public or protected member functions of the base class.
The visibility mode (public, private, protected) in inheritance controls how base class members appear in the derived class.
In public inheritance, public members of the base class remain public, and protected members remain protected in the derived class.
In private inheritance, public and protected members of the base class become private in the derived class.
In protected inheritance, public members of the base class become protected in the derived class.
When an object of a derived class is created, the base class constructor runs first, then the derived class constructor.
When the object is destroyed, the derived class destructor runs first, then the base class destructor.
Inheritance establishes an “is-a” relationship, for example: Dog is an Animal, Student is a Person.
Using virtual functions with inheritance allows runtime polymorphism, where the correct function version is chosen based on the actual object type at runtime.
Syntax
class BaseClass {
// data members
// member functions
};
class DerivedClass : access_specifier BaseClass {
// additional data members
// additional member functions
};
Example
#include <iostream>
using namespace std;
class Animal {
public:
void eat() {
cout << "Animal is eating" << endl;
}
};
class Dog : public Animal {
public:
void bark() {
cout << "Dog is barking" << endl;
}
};
int main() {
Dog d;
d.eat();
d.bark();
return 0;
}
Code Flow Explanation
The class Animal is defined with one public member function eat().
The class Dog is defined as a derived class that publicly inherits from Animal.
Because of public inheritance, Dog automatically gets access to the public function eat() from Animal.
In main(), an object d of type Dog is created.
The call d.eat() executes the function defined in the base class Animal through the derived class object.
The call d.bark() executes the function defined in the derived class Dog.
This shows that a derived class object can use both the inherited features of the base class and its own additional features.
Types of Inheritance
Single Inheritance
Multiple Inheritance
Multilevel Inheritance
Hierarchical Inheritance
Hybrid Inheritance
Single Inheritance
Single inheritance is a type of inheritance in which one derived class inherits from one base class. It forms a simple one-to-one relationship where a single child class extends the features of a single parent class. This structure allows the derived class to reuse the data members and functions of the base class while adding its own properties and functions. Single inheritance is the simplest and most commonly used form of inheritance because it keeps the class hierarchy clear and easy to understand.
Important Points
Only one base class and one derived class participate in this type of inheritance.
It creates a direct “one parent, one child” relationship between classes.
The derived class receives accessible members of the base class according to the chosen visibility mode.
Code reusability is improved because common features stay in the base class.
The derived class can add new features without affecting the base class.
The base class constructor executes first when a derived class object is created.
Function overriding can be used in single inheritance to redefine base class functions in the derived class.
Single inheritance helps maintain a clean and simple class structure, making it suitable for most basic object-oriented designs.
Syntax
class BaseClass {
// base class members
};
class DerivedClass : access_specifier BaseClass {
// derived class additional members
};
Example
#include <iostream>
using namespace std;
class Person {
public:
void showName() {
cout << "Name is displayed" << endl;
}
};
class Student : public Person {
public:
void showCourse() {
cout << "Course is B.Tech CSE" << endl;
}
};
int main() {
Student s;
s.showName();
s.showCourse();
return 0;
}
Code Flow Explanation
The class Person is created with one function showName().
The class Student is defined as a derived class using public inheritance from Person.
Because of public inheritance, Student automatically gets access to the showName() function.
In main(), an object s of Student is created.
The call s.showName() executes the function inherited from the base class Person.
The call s.showCourse() executes the function defined in the derived class Student.
This demonstrates a one-to-one relationship where one derived class inherits from a single base class, which is the core idea of single inheritance.
Multiple Inheritance
Multiple inheritance is a type of inheritance in which a single derived class inherits features from two or more base classes. This allows the derived class to combine and use the properties and functions of multiple parent classes. It is useful when a class needs to represent characteristics coming from different independent sources. Multiple inheritance increases code reusability but must be used carefully because it can create complexity, especially when two base classes have functions or data members with the same name, leading to ambiguity. C++ provides mechanisms to resolve such conflicts using scope resolution or virtual inheritance.
Important Points
In multiple inheritance, one derived class inherits from two or more base classes.
This allows the derived class to gather features from multiple sources.
It can lead to ambiguity if different base classes contain members with the same name.
Ambiguity can be resolved using the scope resolution operator or virtual inheritance.
The order of constructor execution follows the order in which base classes are listed in the derived class definition.
Destructors execute in the reverse order of constructors.
Multiple inheritance increases flexibility but also increases the risk of conflicts, so proper structure and design are important.
It is commonly used when a class needs to combine unrelated functionalities, such as combining features of two different modules.
Syntax
class BaseClass1 {
// members of first base class
};
class BaseClass2 {
// members of second base class
};
class DerivedClass : access_specifier BaseClass1, access_specifier BaseClass2 {
// members of derived class
};
Example
#include <iostream>
using namespace std;
class Teacher {
public:
void teach() {
cout << "Teaching students" << endl;
}
};
class Researcher {
public:
void research() {
cout << "Doing research work" << endl;
}
};
class Professor : public Teacher, public Researcher {
public:
void guide() {
cout << "Guiding students in projects" << endl;
}
};
int main() {
Professor p;
p.teach();
p.research();
p.guide();
return 0;
}
Code Flow Explanation
The class Teacher contains the function teach().
The class Researcher contains the function research().
The class Professor is created as a derived class using public inheritance from both Teacher and Researcher.
Because of multiple inheritance, Professor automatically receives both teach() and research() functions.
In main(), an object p of Professor is created.
The call p.teach() executes the function from the Teacher class.
The call p.research() executes the function from the Researcher class.
The call p.guide() executes the function defined in the Professor class.
This shows how a single derived class can combine features from more than one base class, which is the key idea of multiple inheritance.
Multilevel Inheritance
Multilevel inheritance is a type of inheritance in which a class is derived from another derived class, forming a chain of inheritance. In this structure, the first class acts as the base class for the second class, and the second class becomes the base class for the third class. This creates a parent → child → grandchild relationship. Each derived class inherits features from the class above it and can also add new features. Multilevel inheritance is useful for building step-by-step hierarchical models where each level extends the features of the previous one.
Important Points
It forms a chain of inheritance involving more than two classes.
The first class is inherited by the second class, and the second class is inherited by the third class.
A class can inherit from a class that is already derived from another class.
Each derived class receives accessible members from all classes above it.
Constructors execute from the topmost base class down to the last derived class.
Destructors run in the opposite direction, starting from the last derived class back to the top base class.
Function overriding is commonly used in multilevel inheritance to modify inherited behavior at different stages.
It helps create deeply structured class hierarchies similar to real-world relationships.
Syntax
class BaseClass {
// members of base class
};
class IntermediateClass : access_specifier BaseClass {
// members of intermediate class
};
class DerivedClass : access_specifier IntermediateClass {
// members of final derived class
};
Example
#include <iostream>
using namespace std;
class LivingBeing {
public:
void breathe() {
cout << "Breathing" << endl;
}
};
class Animal : public LivingBeing {
public:
void move() {
cout << "Moving" << endl;
}
};
class Dog : public Animal {
public:
void bark() {
cout << "Barking" << endl;
}
};
int main() {
Dog d;
d.breathe();
d.move();
d.bark();
return 0;
}
Code Flow Explanation
The class LivingBeing defines the function breathe().
The class Animal publicly inherits from LivingBeing, so it receives the breathe() function and adds its own move() function.
The class Dog publicly inherits from Animal, so it receives both breathe() and move() from the classes above it, and adds its own bark() function.
In main(), an object d of Dog is created.
The call d.breathe() works because it comes from LivingBeing.
The call d.move() works because it comes from Animal.
The call d.bark() works because it comes from Dog.
This demonstrates a multilevel chain where each class builds on the previous class, forming a parent → child → grandchild relationship.
Hierarchical Inheritance
Hierarchical inheritance is a type of inheritance in which one base class is inherited by two or more derived classes. This means multiple child classes share the same parent class. Each derived class gets access to the base class members but can also define its own additional features. This type of inheritance is useful when different specialized classes need the common features of a single general class. It helps maintain a clear structure where all child classes originate from one common parent.
Important Points
One base class acts as the parent for multiple derived classes.
Each derived class inherits the accessible members of the same base class.
Derived classes can add their own specific features while sharing common functionality from the base class.
If the base class has private members, they must be accessed through public or protected functions.
Constructors of the base class run first when any derived class object is created.
Each derived class has an independent copy of base class members.
This type supports creating categories where many child classes share the same general properties.
Useful for modeling real-world relationships like: Vehicle → Car, Bike, Bus.
Syntax
class BaseClass {
// base class members
};
class DerivedClass1 : access_specifier BaseClass {
// first derived class members
};
class DerivedClass2 : access_specifier BaseClass {
// second derived class members
};
Example
#include <iostream>
using namespace std;
class Shape {
public:
void draw() {
cout << "Drawing a shape" << endl;
}
};
class Circle : public Shape {
public:
void area() {
cout << "Area of Circle" << endl;
}
};
class Rectangle : public Shape {
public:
void area() {
cout << "Area of Rectangle" << endl;
}
};
int main() {
Circle c;
Rectangle r;
c.draw();
c.area();
r.draw();
r.area();
return 0;
}
Code Flow Explanation
The class Shape is the base class containing the function draw().
The classes Circle and Rectangle publicly inherit from Shape.
Because of hierarchical inheritance, both Circle and Rectangle automatically receive the draw() function.
In main(), objects of Circle and Rectangle are created separately.
The calls c.draw() and r.draw() execute the base class function inherited by both derived classes.
The calls c.area() and r.area() execute the functions defined in their own respective derived classes.
This shows how multiple derived classes share the same base class while adding their own specific behavior.
Hybrid Inheritance
Hybrid inheritance is a combination of two or more types of inheritance used together in a single program. It mixes forms such as single, multiple, multilevel, or hierarchical to build more complex class relationships. The goal is to reuse features from different classes while designing large systems that need multiple levels or multiple sources of inheritance. Hybrid inheritance often leads to the diamond problem, where the same base class appears more than once in the inheritance path. C++ handles this issue using virtual inheritance, ensuring only one copy of the base class is inherited.
Important Points
Hybrid inheritance combines multiple inheritance styles in a single class structure.
The structure may include combinations like: multilevel + multiple, or hierarchical + single, and so on.
It helps design large real-world models where classes share common base features while having specialized extensions.
Hybrid inheritance can create ambiguity if the same base class is inherited more than once.
The diamond problem occurs when two parent classes inherit from the same base class and the derived class inherits from both parent classes.
Virtual inheritance is used to avoid duplicating base class members in the diamond structure.
Constructors in hybrid systems run in a particular order based on inheritance paths and virtual inheritance rules.
It creates flexible but complex class hierarchies, so proper design is important.
Syntax
class Base {
// base class members
};
class A : virtual public Base {
// class A members
};
class B : virtual public Base {
// class B members
};
class Derived : public A, public B {
// derived class members
};
Example
#include <iostream>
using namespace std;
class Device {
public:
void info() {
cout << "Device Information" << endl;
}
};
class Mobile : virtual public Device {
public:
void call() {
cout << "Making a call" << endl;
}
};
class Camera : virtual public Device {
public:
void capture() {
cout << "Capturing photo" << endl;
}
};
class SmartPhone : public Mobile, public Camera {
public:
void apps() {
cout << "Running apps" << endl;
}
};
int main() {
SmartPhone s;
s.info();
s.call();
s.capture();
s.apps();
return 0;
}
Code Flow Explanation
The class Device is the top-level base class with the function info().
The classes Mobile and Camera both inherit from Device using virtual inheritance.
Virtual inheritance ensures only one copy of Device’s members is inherited, preventing duplication.
The class SmartPhone inherits from both Mobile and Camera, forming a hybrid structure (multiple + virtual).
In main(), an object s of SmartPhone is created.
The call s.info() works because virtual inheritance ensures a single shared Device base.
The calls s.call() and s.capture() come from the intermediate classes Mobile and Camera.
The call s.apps() runs the function defined in SmartPhone.
This demonstrates how hybrid inheritance combines different inheritance types while maintaining clarity using virtual inheritance.
Constructor in Inheritance
In inheritance, a constructor is responsible for initializing the object of a class. When a derived class object is created, the constructor of the base class executes first and then the constructor of the derived class. This ensures that the inherited members of the base class are properly initialized before the derived class adds its own initialization. Constructors are not inherited, but the derived class can call the base class constructor using an initialization list. This mechanism ensures a proper order of construction and maintains the integrity of object creation in an inheritance hierarchy.
Important Points
Constructors are not inherited by the derived class.
When a derived class object is created, the base class constructor runs first, then the derived class constructor.
The order of constructor calling follows the order of inheritance, not the order written inside the derived class.
A derived class can call a specific base class constructor using an initializer list.
If no constructor is explicitly called by the derived class, the default constructor of the base class is called automatically.
In multiple inheritance, constructors of base classes execute in the order they appear in the inheritance list.
In multilevel inheritance, constructors execute from the topmost base class down to the final derived class.
If the base class has no default constructor, the derived class must call an appropriate base class constructor.
Virtual base class constructors run before non-virtual base class constructors in hybrid structures.
Destructors run in the reverse order of constructors.
Syntax
class Base {
public:
Base(parameters) {
// base class constructor
}
};
class Derived : public Base {
public:
Derived(parameters) : Base(arguments) {
// derived class constructor
}
};
Example
#include <iostream>
using namespace std;
class Person {
public:
Person() {
cout << "Base Class Constructor: Person" << endl;
}
};
class Student : public Person {
public:
Student() {
cout << "Derived Class Constructor: Student" << endl;
}
};
int main() {
Student s;
return 0;
}
Code Flow Explanation
The class Person defines a constructor that prints a message.
The class Student publicly inherits from Person and defines its own constructor.
In main(), an object s of Student is created.
When the Student object is constructed, C++ first calls the Person constructor because it is the base class.
After finishing the Person constructor, the program calls the Student constructor.
Output shows the order of constructor execution: base class first, then derived class.
This confirms the rule that base class initialization always happens before derived class initialization in inheritance.
Parameterized Constructor in Inheritance
A parameterized constructor is a constructor that takes one or more arguments. In inheritance, when a derived class object is created, the derived class must pass values to the base class parameterized constructor. Since constructors are not inherited, the base class parameterized constructor must be called using the initializer list of the derived class. If the derived class does not explicitly call it, the compiler will throw an error because the base class has no default constructor to call automatically. Using parameterized constructors helps initialize both base and derived class data in an organized way.
Important Points
A parameterized constructor accepts arguments to initialize object data.
If the base class has only a parameterized constructor and no default constructor, the derived class must pass values to it.
The derived class calls the base class constructor using the initializer list.
Constructor order remains the same: base class constructor executes first, then the derived class constructor.
Parameter values allow each part of the object (base and derived) to initialize its own data properly.
Not using initializer list will cause errors when no default constructor exists in the base class.
Syntax
class Base {
public:
Base(parameters) {
// initialization
}
};
class Derived : public Base {
public:
Derived(parameters) : Base(arguments) {
// derived initialization
}
};
Simple Example
#include <iostream>
using namespace std;
class Person {
public:
string name;
// Parameterized constructor of base class
Person(string n) {
name = n;
cout << "Base Constructor: Name set to " << name << endl;
}
};
class Student : public Person {
public:
int roll;
// Parameterized constructor of derived class
Student(string n, int r) : Person(n) { // calling base constructor
roll = r;
cout << "Derived Constructor: Roll set to " << roll << endl;
}
};
int main() {
Student s("Rahul", 23);
return 0;
}
Code Flow Explanation
The base class Person has a parameterized constructor that sets the name.
The derived class Student has its own parameterized constructor that sets the roll number.
The Student constructor uses an initializer list to call
Person(n)and pass the name.In main(), an object s of Student is created with values
"Rahul"and23.First, the Person constructor runs and initializes the name.
Next, the Student constructor runs and initializes the roll number.
Output clearly shows the order: base class constructor first, then derived class constructor.
This example shows how parameterized constructors work smoothly in inheritance using the initializer list.
How to Call Base Class Constructor from Derived Class
When a derived class object is created, the base class constructor must run first. To call a specific base class constructor, especially a parameterized constructor, the derived class uses an initializer list. This is the only correct way to pass values to the base class constructor because constructors are not inherited and cannot be called directly from inside the derived class body.
Important Points
A derived class calls the base class constructor using the initializer list.
This happens before the body of the derived class constructor executes.
The syntax is:
Derived(parameters) : Base(arguments) { }This is required when the base class has no default constructor.
Without using initializer list, the base class parameterized constructor cannot be called.
The base class constructor always runs before the derived class constructor.
Syntax
class Base {
public:
Base(int a) {
// base constructor
}
};
class Derived : public Base {
public:
Derived(int x) : Base(x) { // calling base constructor
// derived constructor
}
};
Simple Example
#include <iostream>
using namespace std;
class Person {
public:
Person(string name) {
cout << "Base Constructor: " << name << endl;
}
};
class Student : public Person {
public:
Student(string name) : Person(name) { // calling base class constructor
cout << "Derived Constructor: Student created" << endl;
}
};
int main() {
Student s("Rahul");
return 0;
}
Code Flow Explanation
The base class Person has a parameterized constructor that receives a name.
The derived class Student uses the initializer list
: Person(name)to call the base class constructor.When the object
sis created, C++ first executes the base class constructor.After that, the derived class constructor runs.
This is how a specific base class constructor is correctly called from a derived class.
Polymorphism
Polymorphism is an important concept in object-oriented programming that allows the same function name or the same operator to behave differently depending on the type of object that uses it. It enables one interface to represent different forms of behavior. This helps write flexible and reusable code because the exact action performed by a function is decided either during compilation or during program execution. In inheritance-based programs, polymorphism allows a base class pointer or reference to call derived class functions depending on the actual object.
Types of Polymorphism
Compile-time Polymorphism
Function Overloading
Operator Overloading
Run-time Polymorphism
Virtual Function
Compile-Time Polymorphism
Compile-time polymorphism is a type of polymorphism where the function to be executed is decided during compilation. This means the compiler determines which function or operator version should run based on the arguments or expression used. It provides faster execution because the binding happens before the program runs. Compile-time polymorphism mainly works through overloading, where multiple functions or operators share the same name but differ in parameters or behavior.
Types of Compile-Time Polymorphism
Function Overloading
Operator Overloading
Function Overloading
Function overloading allows multiple functions to have the same name but different parameter lists. The compiler decides which function to call based on the number, type, or order of arguments.
(We will explain full definition, syntax, examples later when you say.)
Operator Overloading
Operator overloading allows existing operators to work with user-defined types. It lets operators behave differently depending on the objects they operate on. Based on how many operands they work with, operators are divided into two groups.
Types of Operator Overloading
Unary Operator Overloading
Binary Operator Overloading
Unary Operator Overloading
Unary operators operate on one operand. They include operators like:
++
!
(unary minus)
Unary operators can be overloaded in two ways:
Member Function Implementation
Friend Function Implementation
(We will write code and explanation when you say.)
Binary Operator Overloading
Binary operators operate on two operands. Examples include:
+
/
==
!=
Binary operators can also be overloaded in two ways:
Member Function Implementation
Friend Function Implementation
Operator Overloading
Operator overloading is a feature of C++ that allows existing operators such as +, –, *, ==, <<, >> and others to work with user-defined types like classes and structures. Instead of performing operations only on basic data types, an operator can be redefined to perform meaningful actions on objects. This helps make code natural and readable, because operations on objects become similar to operations on built-in types. Operator overloading does not change the original meaning of the operator; it only extends the operator so it can work with objects in a logical way.
Important Points
Operators can be overloaded to work with objects of a class.
Only existing operators can be overloaded; new symbols cannot be created.
Some operators cannot be overloaded (., ::, ?:, sizeof, .*).
Operator overloading improves readability but must be used logically.
Operator overloading is implemented through a special function called the operator function.
The operator function can be written as:
Member function
Friend function
Unary operators take no arguments in member form; binary operators take one argument in member form.
At least one operand must be a user-defined type.
Precedence and associativity of operators do not change after overloading.
Syntax (General)
// Member function form
return_type operator symbol (parameters) {
// body
}
// Friend function form
friend return_type operator symbol (class_name obj1, class_name obj2);
Simple Example (Binary + Operator)
#include <iostream>
using namespace std;
class Number {
public:
int value;
Number(int v = 0) {
value = v;
}
// Overloading + operator using member function
Number operator+(Number obj) {
Number temp;
temp.value = value + obj.value;
return temp;
}
};
int main() {
Number n1(10), n2(20);
Number n3 = n1 + n2; // calls operator+
cout << n3.value << endl;
return 0;
}
Code Flow Explanation
The class Number stores an integer value.
The operator function
operator+()is defined to add two Number objects.In main(), two objects n1 and n2 are created with values 10 and 20.
The expression
n1 + n2automatically calls the operator+ function.Inside the operator function, their values are added and stored in a temporary object.
The temporary object is returned and stored in n3.
Finally, n3.value prints the result 30.
Program: Adding Two Complex Numbers Using Constructor and Operator Overloading
#include <iostream>
using namespace std;
class Complex {
private:
float real, imag;
public:
// Parameterized constructor
Complex(float r = 0, float i = 0) {
real = r;
imag = i;
}
// Overloading + operator
Complex operator+(Complex obj) {
Complex temp;
temp.real = real + obj.real;
temp.imag = imag + obj.imag;
return temp;
}
void show() {
cout << real << " + " << imag << "i" << endl;
}
};
int main() {
Complex c1(3.5, 2.5); // first complex number
Complex c2(1.2, 3.8); // second complex number
Complex c3 = c1 + c2; // calls operator+
cout << "Result: ";
c3.show();
return 0;
}
Code Flow Explanation
The class Complex has two data members: real and imaginary parts.
A parameterized constructor initializes each complex number with given values.
The operator function operator+() adds the real parts together and the imaginary parts together.
Inside main():
c1 is created with (3.5, 2.5)
c2 is created with (1.2, 3.8)
The expression
c1 + c2calls the overloadedoperator+().The function returns a new Complex object containing the sum of real parts and sum of imaginary parts.
The result is printed using the show() function.
Adding Two Complex Numbers Using Operator Overloading
Operator overloading allows the + operator to work with objects of a user-defined class. When adding complex numbers, we overload the + operator so that it adds the real parts together and the imaginary parts together. This makes the addition of objects look natural, just like adding normal numbers.
Syntax
ClassName operator+(ClassName obj) {
// add object values
}
Example Program
#include <iostream>
using namespace std;
class Complex {
private:
float real, imag;
public:
// parameterized constructor
Complex(float r = 0, float i = 0) {
real = r;
imag = i;
}
// overloading + operator
Complex operator+(Complex obj) {
Complex temp;
temp.real = real + obj.real;
temp.imag = imag + obj.imag;
return temp;
}
void show() {
cout << real << " + " << imag << "i";
}
};
int main() {
Complex c1(4.2, 3.1);
Complex c2(1.8, 2.4);
Complex c3 = c1 + c2; // calls operator+
cout << "Sum = ";
c3.show();
return 0;
}
Output
Sum = 6.0 + 5.5i
Code Flow Explanation
Two Complex objects c1 and c2 are created using the constructor.
The statement
c1 + c2calls the overloaded operator+ function.Inside the operator function, the real parts and imaginary parts are added separately.
The result is stored in a temporary Complex object and returned.
The show() function prints the final complex number.
C++ Operators That Cannot Be Overloaded
. (Member access / dot operator)
. (Pointer-to-member operator)*
:: (Scope resolution operator)
?: (Ternary / conditional operator)
sizeof (Size operator)
Unary Operator Overloading
Unary operator overloading allows single-operand operators such as ++, --, -, and ! to work with user-defined objects. These operators perform an operation on one object at a time. By overloading unary operators, we can define how an object should behave when these operators are used. Unary operator overloading can be implemented using either a member function or a friend function.
Important Points
Unary operators work on a single operand.
Common unary operators: ++, --, -, !
They can be overloaded using member functions or friend functions.
When implemented as a member function, unary operator takes no argument.
When implemented as a friend function, unary operator takes one argument (object).
Operator should return the updated object or a temporary object depending on the design.
Operator precedence and associativity do not change after overloading.
Syntax (Using Member Function)
return_type operator symbol() {
// body
}
Syntax (Using Friend Function)
friend return_type operator symbol(class_name obj) {
// body
}
Example (Unary ++ Operator Using Member Function)
#include <iostream>
using namespace std;
class Number {
private:
int value;
public:
Number(int v = 0) {
value = v;
}
// overloading ++ operator (prefix)
Number operator++() {
value = value + 1;
return *this;
}
void show() {
cout << value << endl;
}
};
int main() {
Number n(5);
++n; // calls operator++()
n.show(); // output: 6
return 0;
}
Code Flow Explanation
The class Number stores an integer value.
The unary ++ operator is overloaded using a member function, so it takes no arguments.
When the statement
++nis executed, the operator++() function runs and increments the value.The updated object is returned and printed using show().
This demonstrates unary operator behavior implemented through a member function.
Binary Operator Overloading
Binary operator overloading allows operators that work on two operands (such as +, –, *, /, ==, !=) to operate on user-defined objects. By overloading a binary operator, we define how two objects interact when the operator is used between them. Binary operator overloading can be implemented using either a member function or a friend function, depending on whether the left operand should belong to the class or not.
Important Points
Binary operators require two operands.
Common binary operators: +, –, *, /, ==, !=
Overloading can be done using member or friend functions.
As a member function, the operator takes one argument (right operand).
As a friend function, it takes two arguments (both objects).
At least one operand must be a user-defined type.
Operator does not change precedence or associativity.
Useful for operations like adding objects, comparing objects, or combining object data.
Syntax (Using Member Function)
return_type operator symbol(class_name obj) {
// body
}
Syntax (Using Friend Function)
friend return_type operator symbol(class_name obj1, class_name obj2) {
// body
}
Example (Overloading + Operator Using Member Function)
#include <iostream>
using namespace std;
class Number {
private:
int value;
public:
Number(int v = 0) {
value = v;
}
// overloading + operator
Number operator+(Number obj) {
Number temp;
temp.value = value + obj.value;
return temp;
}
void show() {
cout << value << endl;
}
};
int main() {
Number n1(10), n2(20);
Number n3 = n1 + n2; // calls operator+
n3.show(); // output: 30
return 0;
}
Code Flow Explanation
The class Number stores an integer value.
The + operator is overloaded as a member function, so the left operand (n1) is the calling object.
The right operand (n2) is passed as the function argument.
Inside operator+, the values of n1 and n2 are added and stored in a temporary object.
The temporary object is returned and stored in n3.
The final value is printed using show(), demonstrating binary operator overloading through a member function.
Pre-Increment Operator Overloading
Pre-increment operator overloading allows the ++ operator (prefix form) to work on user-defined objects. In the prefix version (++obj), the value of the object is increased first, and then the updated object is returned. Overloading this operator makes object manipulation natural and similar to built-in types. The pre-increment operator can be overloaded using either a member function or a friend function.
Important Points
Pre-increment form is written as ++obj.
In prefix form, the value is updated before returning the object.
As a member function, it takes no argument.
As a friend function, it takes one argument (object).
Must return the updated object (usually by value).
Used when an object represents a numeric value or counter-like behavior.
Syntax (Using Member Function)
return_type operator++() {
// update value
return *this;
}
Syntax (Using Friend Function)
friend return_type operator++(class_name &obj) {
// update value
return obj;
}
Example (Pre-Increment Using Member Function)
#include <iostream>
using namespace std;
class Number {
private:
int value;
public:
Number(int v = 0) {
value = v;
}
// overloading pre-increment ++ operator
Number operator++() {
value = value + 1; // increment first
return *this; // return updated object
}
void show() {
cout << value << endl;
}
};
int main() {
Number n(5);
++n; // calls operator++()
n.show(); // output: 6
return 0;
}
Code Flow Explanation
The class Number stores an integer value.
The pre-increment operator is overloaded using a member function, so it takes no parameters.
When
++nis executed, the operator++() function is called.Inside the function, the value is increased by 1.
The updated object is returned.
The show() function prints the incremented value (6).
This demonstrates the behavior of prefix ++ operator overloading.
Pre-Increment Operator Overloading
Pre-increment operator overloading allows the ++obj (prefix) operation to work on user-defined objects. In this form, the value is incremented first and then the updated object is returned. This makes object operations behave like built-in types. The pre-increment operator can be overloaded using a member function or a friend function.
Important Points
Pre-increment is written as ++obj.
The object’s value is increased before returning.
Member function version takes no parameter.
Friend function version takes one reference parameter.
Returns the updated object.
Useful for classes that represent numbers or counters.
Syntax (Using Member Function)
return_type operator++() {
// update value
return *this;
}
Syntax (Using Friend Function)
friend return_type operator++(class_name &obj) {
// update value
return obj;
}
Example (Pre-Increment Using Member Function)
#include <iostream>
using namespace std;
class Number {
private:
int value;
public:
Number(int v = 0) {
value = v;
}
// overloading pre-increment ++ operator
Number operator++() {
value = value + 1;
return *this;
}
void show() {
cout << value << endl;
}
};
int main() {
Number n(5);
++n; // calls operator++()
n.show(); // output: 6
return 0;
}
Output
6
Code Flow Explanation
Number object n is created with value 5.
The expression
++ncalls the overloaded operator++().Inside the operator, value becomes 6.
The updated object is returned.
n.show() prints the final value 6.
Post-Increment Operator Overloading
Post-increment operator overloading allows the obj++ (postfix) operation to work on user-defined objects. In this form, the current value of the object is returned first, and then the object is incremented. To differentiate postfix from prefix, C++ uses an int dummy parameter in the function signature. The post-increment operator can be overloaded using a member function or a friend function.
Important Points
Post-increment is written as obj++.
In postfix form, the old value is returned first, then increment happens.
Member function version uses a dummy int parameter to indicate postfix.
Friend function version also uses a dummy int parameter.
Returns the value before increment (usually a temporary object).
Useful when objects behave like numbers or counters.
Syntax (Using Member Function)
return_type operator++(int) {
// use dummy int to mark postfix
// return old value
}
Syntax (Using Friend Function)
friend return_type operator++(class_name &obj, int) {
// return old value
}
Example (Post-Increment Using Member Function)
#include <iostream>
using namespace std;
class Number {
private:
int value;
public:
Number(int v = 0) {
value = v;
}
// overloading post-increment operator
Number operator++(int) {
Number temp = *this; // save current value
value = value + 1; // increment later
return temp; // return old value
}
void show() {
cout << value << endl;
}
};
int main() {
Number n(5);
Number old = n++; // calls operator++(int)
cout << "Old value: ";
old.show();
cout << "New value: ";
n.show();
return 0;
}
Output
Old value: 5
New value: 6
Code Flow Explanation
A Number object n is created with value 5.
The expression
n++calls the overloaded operator++(int) because of the dummy int.A temporary object temp stores the old value (5).
The value of n is then incremented to 6.
The old value (temp) is returned and stored in old.
Printing old gives 5, printing n gives 6.
This demonstrates how postfix increment returns the previous value but updates the object afterward.
Why We Pass a Dummy Parameter in Post-Increment Operator Overloading
In C++, both pre-increment (++obj) and post-increment (obj++) use the same operator symbol (++).
If we do not differentiate them, the compiler will not know which version to call.
To solve this confusion, C++ uses a rule:
The postfix version must have a dummy int parameter.
This parameter is not used in the function body.
Its only purpose is to tell the compiler that this function is the postfix version of the operator.
Real Concept Behind Dummy int Parameter
The prefix form uses:
operator++()The postfix form uses:
operator++(int)
The int inside postfix acts like a “tag” to mark the function as the postfix version.
Why is it needed?
Both operators use the same name (operator++).
Without a difference in the function signature, the compiler cannot choose the correct one.The dummy int allows function overloading.
With different function signatures, C++ treats them as different functions.It represents how postfix works in built-in types.
For built-in int, prefix ++i and postfix i++ are different operations.
C++ extends this concept to objects.The dummy int has no role in logic.
It is never used inside the function.
It only exists so the compiler can differentiate overloads.
One-Line Exam/Viva Answer
“The dummy int parameter in the postfix increment operator is used only to distinguish it from the prefix version. It allows the compiler to select the correct overloaded function. The parameter has no actual use; it is just a marker.”
Short Concept Summary
Prefix →
operator++()Postfix →
operator++(int)Dummy int → differentiates postfix
Compiler → uses it to resolve overloading
Not used in code → only identifies the operator version
Difference Between Pre-Increment and Post-Increment (++i vs i++)
Pre-Increment (++i) | Post-Increment (i++) |
|---|---|
Value is incremented first, then used. | Value is used first, then incremented. |
Updates the variable immediately before the expression is evaluated. | Updates the variable after the current expression completes. |
Returns the new (incremented) value. | Returns the old (original) value. |
Faster than post-increment because no temporary object is created. | Slower than pre-increment because it stores a temporary copy of the old value. |
Syntax in operator overloading: | Syntax in operator overloading: |
Used when updated value is needed instantly. | Used when the old value is needed before increment. |
No dummy parameter is used. | Requires a dummy int parameter to differentiate postfix from prefix. |
Rules of Operator Overloading in C++
Operator overloading can only be done for existing operators; new symbols cannot be created.
Some operators cannot be overloaded:
.,::,.*,?:,sizeof.At least one operand must be a user-defined type (class or struct).
Operator overloading does not change the precedence or associativity of the operator.
Operator overloading cannot change the number of operands an operator works with.
Operator overloading cannot change the fundamental meaning of an operator (it should behave logically).
Overloaded operators can be implemented as member functions or friend functions.
For unary operators, the member function version takes no argument.
For binary operators, the member function version takes one argument.
Friend function version takes one argument for unary and two arguments for binary operators.
Overloaded operators can return new objects or references depending on design.
Operators like =, [], (), -> must be overloaded as member functions (not as friend functions).
Operator functions cannot have default parameters.
Operator overloading does not inherit automatically; each derived class must overload if needed.
Overloading should maintain clear and predictable behavior to avoid confusing code.
The overloaded operator must be defined for a specific class; it cannot be global without friend access.
Overloading cannot change how many arguments operators take.
You can overload both prefix and postfix increment/decrement separately.
Postfix increment/decrement requires a dummy int parameter to differentiate from prefix.
Friend Function
A friend function is a special function in C++ that is not a member of a class but is allowed to access the private and protected members of that class. It is declared inside the class using the keyword friend, but its definition is written outside the class. Friend functions help when two different classes need to share data or when an operator must work on two objects where the left operand is not an object of the class.
Important Points
A friend function is not a member of the class.
It can access private and protected members of the class.
It is declared using the keyword friend inside the class.
It is defined outside the class without using the scope resolution operator.
Useful for operator overloading (especially binary operators).
Can access multiple class objects together.
Does not need an object to be called; it behaves like a normal function.
It increases flexibility but should be used only when needed because it breaks encapsulation slightly.
Syntax
class ClassName {
private:
int data;
public:
friend return_type functionName(ClassName obj); // declaration
};
// definition outside class
return_type functionName(ClassName obj) {
// can access obj.data
}
Example
#include <iostream>
using namespace std;
class Number {
private:
int value;
public:
Number(int v = 0) {
value = v;
}
// declaring friend function
friend void showValue(Number obj);
};
// friend function definition
void showValue(Number obj) {
cout << "Value = " << obj.value << endl; // accessing private data
}
int main() {
Number n(20);
showValue(n); // calling friend function
return 0;
}
Code Flow Explanation
The class Number stores a private variable value.
The function
showValue()is declared as a friend inside the class.Because of this,
showValue()can accessobj.valueeven though it is private.The friend function is defined outside the class like a normal function.
In main(), an object n is created with value 20.
The call
showValue(n)prints the private data value of the object using friend access.
Program: Access Private Data Using a Friend Function
#include <iostream>
using namespace std;
class Number {
private:
int value; // private data
public:
Number(int v) {
value = v;
}
// friend function declaration
friend void display(Number obj);
};
// friend function definition
void display(Number obj) {
cout << "Private Value = " << obj.value << endl;
}
int main() {
Number n(50);
display(n); // friend function accessing private data
return 0;
}
Output
Private Value = 50
Code Flow Explanation
The class Number has a private variable value.
The function
display()is declared as a friend function inside the class.Because of friend access,
display()can directly accessobj.valueeven though it is private.In main(), the object n is created with value 50.
The function display(n) prints the private value successfully.
Program: Adding Two Numbers Using Friend Function in Operator Overloading
#include <iostream>
using namespace std;
class Number {
private:
int value;
public:
Number(int v = 0) {
value = v;
}
// declaring friend function for overloading +
friend Number operator+(Number n1, Number n2);
};
// friend function definition
Number operator+(Number n1, Number n2) {
Number temp;
temp.value = n1.value + n2.value; // accessing private data
return temp;
}
int main() {
Number a(10), b(20);
Number c = a + b; // calls friend operator function
cout << "Sum = " << c.operator+(a).operator+(b).operator+(c.value) << endl;
return 0;
}Overloading
Overloading is a compile-time feature where two or more functions or operators have the same name but different parameters or behavior. It helps reuse the same name for different purposes depending on the arguments. Overloading improves readability and allows natural operations on user-defined objects.
Important Points
Happens at compile time.
Same name, but parameters differ in number, type, or order.
Does not depend on inheritance.
Includes function overloading and operator overloading.
Return type alone cannot differentiate overloaded functions.
Syntax (Function Overloading)
void fun(int x);
void fun(double y);
Example
#include <iostream>
using namespace std;
void show(int a) {
cout << "Integer: " << a << endl;
}
void show(double b) {
cout << "Double: " << b << endl;
}
int main() {
show(10);
show(5.5);
return 0;
}
Code Flow
Compiler sees two show() functions with different parameter types.
Based on the argument (int or double), the correct function is chosen during compilation.
Overriding
Overriding is a run-time feature where a derived class redefines a function of the base class using the same name, same parameters, and same return type. It allows a derived class to give its own behavior to an inherited function. Overriding works only in inheritance and supports polymorphism when virtual functions are used.
Important Points
Happens at run time.
Requires inheritance (base and derived class).
Function name, return type, and parameters must be exactly same.
Base class function must be marked virtual for runtime polymorphism.
Derived class provides a new version of the same function.
Syntax
class Base {
public:
virtual void show();
};
class Derived : public Base {
public:
void show(); // overriding
};
Example
#include <iostream>
using namespace std;
class Base {
public:
virtual void display() {
cout << "Base class display" << endl;
}
};
class Derived : public Base {
public:
void display() {
cout << "Derived class display" << endl;
}
};
int main() {
Base *p;
Derived d;
p = &d;
p->display();
return 0;
}
Code Flow
Base has a virtual function display().
Derived overrides it with its own display().
Base pointer points to derived object.
Because of virtual function, derived version runs at runtime.
Hiding
Function hiding occurs when a derived class defines a function with the same name as a base class, but with different parameters, or even without parameters. This does not override the base class function—it simply hides it. The base class version does not become accessible through the derived class unless scope resolution is used.
Important Points
Function hiding happens even without virtual functions.
If derived class defines a function with the same name but different parameters, it hides all base class functions with the same name.
To call the base class function, scope resolution is needed:
Base::functionName().It is a compile-time phenomenon.
Syntax
class Base {
public:
void show();
};
class Derived : public Base {
public:
void show(int x); // hides Base::show()
};
Example
#include <iostream>
using namespace std;
class Base {
public:
void display() {
cout << "Base display" << endl;
}
};
class Derived : public Base {
public:
void display(int x) { // hides Base::display
cout << "Derived display: " << x << endl;
}
};
int main() {
Derived d;
d.display(5); // calls derived version
d.Base::display(); // calls base version
return 0;
}
Code Flow
Derived defines display(int), so it hides Base::display().
Calling d.display(5) runs derived version.
To call base version, we use
d.Base::display().
Run-Time Polymorphism
Run-time polymorphism is a feature of C++ where the function that will run is decided during program execution, not during compilation. This allows a base class pointer or reference to call the correct function depending on the actual object it is pointing to. Run-time polymorphism happens through virtual functions and is one of the most powerful features of object-oriented programming.
Important Points
The decision of which function to execute is made at run time.
Requires inheritance (base → derived).
The base class function must be declared as virtual.
Derived class must redefine (override) the same function.
A base class pointer/reference should point to a derived class object.
Achieved using dynamic binding or late binding.
Supports polymorphic behavior in large systems (one interface, multiple behaviors).
Types of Run-Time Polymorphism
Function Overriding
Virtual Functions
Function Overriding
Function overriding is a run-time polymorphism feature in C++ where a derived class provides a new implementation of a function that already exists in the base class with the same name, same return type, and same parameter list. It allows a derived class to change or extend the behavior of a function inherited from its base class. Overriding only works in inheritance and is used when different classes need to provide different versions of the same function.
Important Points
The function name, parameter list, and return type must be exactly the same in both base and derived class.
Must occur in an inheritance relationship.
Base class function should be declared with the virtual keyword to achieve true run-time polymorphism.
The derived class redefines the same function, giving it its own behavior.
When a base pointer points to a derived object, the derived version of the function is executed.
Binding happens at run time, also called dynamic binding or late binding.
Overriding does not affect access to base class data members.
Overriding works only with non-static member functions; static functions cannot be overridden.
Constructors and destructors cannot be overridden.
A base class pointer must be used to see polymorphic behavior; otherwise, normal function calling happens.
Overriding supports extensibility — the derived class gives more specific behavior.
Rules of Function Overriding
Base and derived class must have same function signature (same name + same parameters + same return type).
Base class function should be declared as virtual.
A derived class can optionally use the override keyword (C++11) for clarity.
Access specifier of the overridden function can be different (not required to match).
If signature changes even slightly, it becomes hiding, not overriding.
Base pointer/reference must point to derived object to observe overriding behavior.
Syntax
class Base {
public:
virtual void show() {
// base class implementation
}
};
class Derived : public Base {
public:
void show() override { // overriding
// derived class implementation
}
};
Example Program
#include <iostream>
using namespace std;
class Shape {
public:
virtual void draw() { // virtual function
cout << "Drawing Shape" << endl;
}
};
class Circle : public Shape {
public:
void draw() override { // overriding
cout << "Drawing Circle" << endl;
}
};
int main() {
Shape *ptr;
Circle c;
ptr = &c; // base pointer → derived object
ptr->draw(); // calls derived version
return 0;
}
Output
Drawing Circle
Code Flow Explanation
The base class Shape contains the virtual function draw().
The derived class Circle overrides the draw() function with its own version.
In main(), a base class pointer (ptr) is used to point to a Circle object.
Because the base function is marked virtual, C++ checks the type of the actual object during run time.
The pointer points to a derived object (Circle), so the derived version of draw() is executed.
This demonstrates run-time polymorphism through function overriding.
Key Notes
Overriding = same function name + same parameters + same return type in inheritance.
Always mark base class function as virtual to enable polymorphism.
Derived class function replaces the base class function at run time.
If function signature changes, it becomes function hiding, not overriding.
Overriding supports flexible and extensible OOP designs.
When we use function overriding, the real goal is to achieve run-time polymorphism, where the correct function is chosen based on the actual object type during program execution. A base class pointer allows this because it can point to objects of the derived class, and when the base function is marked as virtual, the program checks the real object at run time and calls the derived version of the function. Without using a pointer (or reference), the compiler decides the function during compile time, and overriding will not work as intended. In simple words, we use pointers so the program can decide at run time which function to run based on the object, not based on the variable type.
Virtual Function
A virtual function is a member function of a base class that is declared using the keyword virtual so that C++ chooses the correct function to call at run time depending on the type of object being referenced. When a base class pointer or reference points to a derived class object, the virtual function ensures that the derived class version of the function is executed, even when accessed through the base pointer. This enables run-time polymorphism, allowing different classes to respond differently using the same function name.
Important Points
Declared in the base class using the keyword virtual.
Allows a base pointer to call derived class functions at run time.
Enables dynamic binding or late binding.
Must be overridden in the derived class to show polymorphism.
Function signature must match in both base and derived classes.
Virtual functions cannot be static.
Constructors cannot be virtual, but destructors usually are (to avoid memory leaks).
If a derived class does not override the function, the base class version is used.
Works only through pointers or references.
Helps in designing flexible and extensible programs.
Syntax
class Base {
public:
virtual void display() {
// base implementation
}
};
class Derived : public Base {
public:
void display() { // overriding
// derived implementation
}
};
Simple Example
#include <iostream>
using namespace std;
class Base {
public:
virtual void show() {
cout << "Base show" << endl;
}
};
class Derived : public Base {
public:
void show() {
cout << "Derived show" << endl;
}
};
int main() {
Base *ptr;
Derived d;
ptr = &d;
ptr->show(); // calls Derived show()
return 0;
}
Output
Derived show
Code Flow Explanation
The base class has a virtual function show().
The derived class redefines show() with its own behavior.
A base pointer is used to point to a derived object.
Because show() is virtual, C++ checks the actual object at run time.
Since the pointer points to a Derived object, the derived version executes.
This demonstrates real run-time polymorphism.
Why We Use Virtual Functions
We use virtual functions because they allow C++ to decide at run time which function to call when a base class pointer refers to a derived class object. Without the virtual keyword, the compiler decides the function call during compile time, based only on the pointer type, not on the object type. Virtual functions make the program flexible by ensuring that the correct overridden function from the derived class is executed, giving true run-time polymorphism.
What Happens If We Do NOT Use Virtual Functions
If we do not use virtual, then when a base class pointer points to a derived class object and calls a function, the base class version will run, not the derived version. This is because the compiler binds the function call at compile time using the pointer type, not the actual object. As a result, overriding will not work as intended, and the program loses polymorphism.
Example of what happens without virtual:
Base pointer → Derived object → Base version runs
Derived version is ignored
Only compile-time binding happens
No polymorphism
What Happens When We Use Virtual Functions
When the function in the base class is marked as virtual, C++ delays the decision of which function to call until run time. It checks the actual object that the pointer is pointing to and then calls the correct overridden function of the derived class. This is called dynamic binding or late binding.
Result when using virtual:
Base pointer → Derived object → Derived version runs
Correct overridden function is selected at run time
True run-time polymorphism works
Simple Summary for Exams
Without virtual: base pointer always calls base version.
With virtual: base pointer calls derived version.
Reason: virtual tells the compiler to choose the function based on the object, not the pointer type.
Is a Friend Function Related to Virtual Function or Run-Time Polymorphism?
No.
A friend function has nothing to do with virtual functions, overriding, or run-time polymorphism.
Why Friend Function Is Not Related
A friend function is not a member of the class.
Only member functions can be virtual.
Virtual functions work only with inheritance + overriding + base pointer.
A friend function is just a normal function with extra access, not part of inheritance.
Key Differences
Friend Function
Not a class member
Cannot be virtual
No overriding
No run-time polymorphism
Used to access private data
Used often in operator overloading
Virtual Function
Must be a class member
Supports overriding
Supports run-time polymorphism
Requires base pointer/reference
Used to determine function at run time
Very Simple Exam Answer
“Friend functions do not participate in overriding or polymorphism because they are not class members. Therefore, they cannot be virtual and have no relation to run-time polymorphism.”
Pure Virtual Function
A pure virtual function is a virtual function in a base class that has no definition in that class. It is created only to be overridden by the derived classes. A pure virtual function forces the derived classes to provide their own implementation. Any class that contains at least one pure virtual function becomes an abstract class, which means objects of that class cannot be created.
When Do We Use Pure Virtual Functions?
When the base class should not provide any implementation of a function.
When different derived classes must provide their own version of the function.
When we want to create a common interface for all derived classes.
When we want to prevent creating objects of the base class.
When the base class is only a concept (like Shape, Vehicle, Animal) and real implementations belong to derived classes.
Important Points
Declared by assigning = 0 in the base class.
Makes the class an abstract class.
Objects of an abstract class cannot be created.
Derived classes must override the pure virtual function.
Supports run-time polymorphism.
Can have constructors, but cannot be instantiated.
The base class may still provide common data members and helper functions.
Syntax
class Base {
public:
virtual void show() = 0; // pure virtual function
};
Example
#include <iostream>
using namespace std;
class Shape {
public:
virtual void draw() = 0; // pure virtual function
};
class Circle : public Shape {
public:
void draw() {
cout << "Drawing Circle" << endl;
}
};
int main() {
Shape *s; // base pointer allowed
Circle c;
s = &c;
s->draw(); // calls Circle's draw()
return 0;
}
Code Flow Explanation
The class Shape declares draw() as a pure virtual function using
= 0.Shape becomes an abstract class, so we cannot create an object of Shape.
The class Circle overrides draw() and gives its own definition.
In main(), a base class pointer of type Shape is used to point to a Circle object.
Because draw() is pure virtual and overridden, the derived class version executes.
This shows how pure virtual functions create a common interface while forcing derived classes to implement the function.
Abstract Class
An abstract class is a class that contains at least one pure virtual function and cannot be used to create objects. It only provides a base structure or a common interface for derived classes. Abstract classes are used when the base class represents a general concept, and the actual implementation must be provided by derived classes. They help enforce that all derived classes follow the same function pattern while allowing them to implement the function in their own way.
When Do We Use an Abstract Class?
When the base class is a concept and should not create objects itself (like Shape, Animal, Vehicle).
When every derived class must implement a specific function.
When we want a common interface for all derived classes.
When we want to achieve run-time polymorphism through inheritance.
When we want to enforce a rule in derived classes.
Important Points
A class containing at least one pure virtual function becomes an abstract class.
Objects of an abstract class cannot be created.
We can create pointers or references of an abstract class.
Derived classes must override the pure virtual function.
Abstract classes can contain:
normal functions
constructors
destructors
data members
If a derived class does not override all pure virtual functions, it also becomes abstract.
Used for designing large systems with common behavior and multiple specialized classes.
Syntax
class Base {
public:
virtual void show() = 0; // pure virtual function
};
Example
#include <iostream>
using namespace std;
class Shape {
public:
virtual void draw() = 0; // pure virtual function
};
class Rectangle : public Shape {
public:
void draw() {
cout << "Drawing Rectangle" << endl;
}
};
int main() {
Shape *s; // allowed
Rectangle r;
s = &r; // base pointer points to derived
s->draw(); // calls Rectangle's draw()
return 0;
}
Output
Drawing Rectangle
Code Flow Explanation
Shape contains a pure virtual function draw(), so Shape becomes an abstract class.
Objects of Shape cannot be created, but pointers of Shape are allowed.
Rectangle inherits from Shape and provides its own draw() function.
In main(), a Shape pointer points to a Rectangle object.
Because draw() is overridden, the Rectangle version runs.
This shows how an abstract class forces derived classes to provide implementation and supports run-time polymorphism.
Difference Between Friend Function and Member Function
Friend Function | Member Function |
|---|---|
Not a member of the class. | A regular function inside the class. |
Declared using the | No special keyword is required. |
Defined outside the class. | Defined inside or outside using scope resolution. |
Can access private and protected data. | Can access all class members directly. |
Cannot be virtual. | Can be virtual or non-virtual. |
Does not support overriding or polymorphism. | Supports overriding and polymorphism. |
Called like a normal function. | Called using object or object pointer. |
Used mostly for operator overloading or accessing private data. | Used for behavior and operations of the class. |
Difference Between Virtual Function and Pure Virtual Function
Virtual Function | Pure Virtual Function |
|---|---|
Has a definition in the base class. | Has no definition in the base class. |
Declared with the keyword | Declared as |
Does not make the class abstract. | Makes the class abstract immediately. |
Base class object can be created. | Base class object cannot be created. |
Derived class may override it. | Derived class must override it. |
Supports run-time polymorphism when overridden. | Forces run-time polymorphism and acts as an interface. |
Used when base class provides common behavior. | Used when base class should not provide behavior. |
Optional to override in derived class. | Mandatory to override in derived class. |
Virtual Base Class
A virtual base class is a special type of inheritance used to avoid duplicate copies of a base class when multiple paths of inheritance lead back to the same base class. It is mainly used in diamond inheritance (a situation where two classes inherit from one base class, and another class inherits from both). By declaring the base class as virtual, C++ ensures that only one shared copy of the base class members is inherited by the bottom-most derived class. This prevents ambiguity, memory duplication, and confusion.
Why Do We Use Virtual Base Class?
To solve the diamond problem in multiple inheritance.
To avoid multiple copies of the same base class in a derived class.
To prevent ambiguity errors like:
obj.value(which value to access?).To ensure one common base object is shared across the inheritance hierarchy.
To maintain consistency and reduce memory wastage.
Important Points
Declared using the keyword virtual before the base class name.
Ensures only one instance of the base class is inherited.
Used mainly when multiple inheritance leads back to the same base class.
The virtual base class is constructed before non-virtual base classes.
Prevents ambiguity and duplicate data members.
Commonly used in diamond-shaped inheritance structures.
Syntax
class Base {
// base class members
};
class A : virtual public Base {
// A inherits Base virtually
};
class B : virtual public Base {
// B inherits Base virtually
};
class Derived : public A, public B {
// Derived now gets only one Base
};
Example
#include <iostream>
using namespace std;
class Base {
public:
int value;
Base() {
value = 10;
}
};
class A : virtual public Base { };
class B : virtual public Base { };
class Derived : public A, public B {
public:
void show() {
cout << "Value = " << value << endl;
}
};
int main() {
Derived d;
d.show();
return 0;
}
Output
Value = 10
Code Flow Explanation
Base class has one data member
value.A and B both inherit Base virtually, so they do not create separate copies of Base.
The Derived class inherits from both A and B.
Because of virtual inheritance, Derived receives one shared copy of Base.
In main(), object d of Derived is created.
The value from Base is accessed through this single shared instance.
The output shows only one copy of the Base class is present.
Function Template
A function template is a special type of function in C++ that allows the same function to work with different data types without rewriting it multiple times. Instead of writing separate functions for int, float, double, or other types, a single template function can generate the required version automatically. Templates support generic programming, meaning the data type becomes a variable decided at the time of calling the function. This saves time, removes duplication, and makes programs flexible.
Why Do We Use Function Templates?
To avoid writing the same function again and again for different data types.
To create functions that work with any type (int, float, char, double, etc.).
To reduce code duplication.
To make programs more flexible and reusable.
To support generic programming.
Important Points
Defined using the keyword template.
Uses a placeholder type like T, typename T, or class T.
The actual data type is decided when the function is called.
Compiler creates (instantiates) the required version of the function automatically.
Can have multiple template parameters (T1, T2, etc.).
Template functions can also be overloaded.
Works for user-defined types if operators are defined properly.
Reduces code size and improves maintainability.
Syntax
template <class T>
T functionName(T a, T b) {
// function body
}
or
template <typename T>
T functionName(T a, T b) {
// function body
}
Example: Template to Find Maximum of Two Values
#include <iostream>
using namespace std;
template <class T>
T myMax(T a, T b) {
return (a > b) ? a : b;
}
int main() {
cout << myMax(10, 20) << endl; // int
cout << myMax(5.5, 2.3) << endl; // float
cout << myMax('A', 'Z') << endl; // char
return 0;
}
Output
20
5.5
Z
Code Flow Explanation
The template function myMax() is defined using
template <class T>.When calling myMax(10, 20), the compiler replaces T with int and generates an int version.
When calling myMax(5.5, 2.3), the compiler generates a float version.
When calling myMax('A', 'Z'), the compiler creates a char version.
The ternary operator compares the values and returns the larger one.
The same function works for multiple data types without rewriting.
Advantages of Function Templates
Code reusability — only one function works for all types.
No duplication of similar functions.
Easy to maintain and update.
Efficient and type-safe.
Extensible — works with user-defined classes if operators are overloaded.
Understanding Function Template Syntax (Step-by-Step)
1. Template Header
The first line is always:
template <class T>
or
template <typename T>
Both class and typename mean the same thing in templates.
They represent a placeholder type (a temporary datatype name).
What is <T>
<and>are the template brackets.Everything inside them is a list of template parameters.
Tis the name of a template type parameter (a variable that holds a datatype).
Think of T as a “datatype variable.”
Example:
If you call
myMax(10, 20)then T = intIf you call
myMax(5.5, 7.2)then T = floatIf you call
myMax('A', 'C')then T = char
The compiler replaces T with the actual datatype when the function is used.
2. Function Definition
Example:
T myMax(T a, T b) {
return (a > b) ? a : b;
}
What does this mean?
T myMax(...) → The return type is T (datatype decided later)
T a, T b → The function takes two parameters, both of type T
(datatype decided later)return a or b → Works for any datatype that supports
>operator
Why do we use T a, T b?
Because the function must work for any type, but we don’t know the type yet.
So we use T to represent the datatype.
Example:
If T is int → a and b become int
If T is float → a and b become float
If T is char → a and b become char
3. How Does a Template Function Work?
When you call the function, the compiler looks at the datatype of arguments and automatically creates the right version of the function.
Example:
myMax(10, 20);
Compiler sees both arguments are int, so it creates:
int myMax(int a, int b)
For:
myMax(5.5, 3.2);
Compiler creates:
double myMax(double a, double b)
This automatic creation of the required function is called template instantiation.
4. Why Do We Use < > in Template?
The < > brackets specify which type the template function should work with.
Example:
myMax<int>(10, 20); // explicitly uses int
myMax<double>(6.5, 4.3); // explicitly uses double
But normally we don’t write <int> because the compiler detects automatically.
Simple Full Example Explained Line-by-Line
template <class T> // T is a placeholder for a datatype
T add(T a, T b) { // a and b are of type T
return a + b; // works for any type that supports +
}
int main() {
cout << add(5, 10); // T becomes int
cout << add(2.5, 3.1); // T becomes float
cout << add('A', 2); // T becomes char
}
What happened?
add(5, 10) → T = int → compiler generates an int version
add(2.5, 3.1) → T = float → compiler generates a float version
add('A', 2) → T = char → compiler generates a char version
One function → Many datatypes → No rewriting.
Final Simple Summary (For Exams)
template <class T>introduces a template.Tis a generic datatype, decided later.T a, T bmeans a and b will match whatever datatype T becomes.< >encloses the template parameter list.Compiler generates the correct version based on the arguments passed.
Function templates support generic programming.
Class Template
A class template is a blueprint that allows you to create a class that works with any datatype. Instead of writing separate classes for int, float, double, or custom types, you write one generic class, and the compiler creates different versions of the class based on the datatype when objects are created. Class templates support generic programming, making code reusable and flexible for multiple data types.
Why Do We Use Class Templates?
To avoid writing multiple classes for different data types.
To create one class that can store, process, or manage any type.
To reduce repetition and enhance code reusability.
To support generic structures like arrays, stacks, queues, linked lists, etc.
To make the class flexible and type-independent.
Important Points
Declared using
template <class T>ortemplate <typename T>.The placeholder type T represents a datatype variable.
T can represent int, float, char, double, string, or even objects.
Class templates are instantiated when an object is created.
Different objects can use different datatypes with the same class template.
Multiple template parameters are allowed (e.g.,
<class T1, class T2>).Member functions can be defined inside or outside the class template.
When defined outside, use scope resolution with template header.
Syntax
template <class T>
class ClassName {
private:
T data; // T used as datatype
public:
ClassName(T value) {
data = value;
}
void show() {
cout << data << endl;
}
};
Example: Class Template to Store and Display a Value
#include <iostream>
using namespace std;
template <class T>
class Box {
private:
T value;
public:
Box(T v) {
value = v;
}
void show() {
cout << "Value = " << value << endl;
}
};
int main() {
Box<int> b1(10); // T = int
Box<double> b2(5.7); // T = double
Box<char> b3('A'); // T = char
b1.show();
b2.show();
b3.show();
return 0;
}
Output
Value = 10
Value = 5.7
Value = A
Code Flow Explanation
The template
<class T>declares T as a generic datatype.The class
Boxuses T for its data member and constructor.When
Box<int> b1(10)is created, the compiler replaces T with int and creates a full class version.Box<double> b2(5.7)creates another version where T becomes double.Box<char> b3('A')creates yet another version for char.Each object uses its own datatype, but the class logic remains the same.
The show() function prints the stored value for each object.
This demonstrates how a class template works for multiple data types using a single definition.
Advantages of Class Templates
Code reusability — one class works for all types.
Reduced duplication and maintenance effort.
Strong type safety — mistakes are caught at compile time.
Flexible — suitable for complex data structures.
Makes generic libraries possible (like STL).
Difference Between Function Template and Class Template
Function Template | Class Template |
|---|---|
Used to create a single function that works with any datatype. | Used to create a whole class that works with any datatype. |
Syntax: | Syntax: |
Generic behavior is applied only to one function. | Generic behavior applies to all members of the class. |
Function is instantiated when it is called. | Class is instantiated when an object is created. |
Easier and simpler to implement. | More complex than function templates. |
Useful for small operations like add, max, swap, compare, etc. | Useful for data structures like stack, queue, array, linked list, etc. |
Supports function overloading. | Supports class template overloading (rare in syllabus). |
Mostly used for simple logic with different types. | Mostly used when a datatype-independent class is needed. |
Advantages of Function Templates
One function works for any datatype (int, float, double, char, etc.).
Avoids writing multiple functions with the same logic.
Reduces code duplication.
Compiler automatically generates required versions based on arguments.
Easy to read and maintain.
Supports type safety—errors detected at compile time.
Useful for small reusable operations like max, min, swap, sort, compare, etc.
Simplifies generic programming for functions.
Advantages of Class Templates
One class works for all data types.
Useful for building generic data structures (stack, queue, vectors, linked lists).
Saves time by removing the need to write separate classes for each datatype.
Allows storing and processing values of any type using one blueprint.
Provides strong type safety and avoids runtime type errors.
Encourages reusable and modular code design.
Template instantiation creates optimized, type-specific classes.
Supports multiple template parameters for flexible class design.