Object-Oriented Programming (OOP) is a programming paradigm that uses “objects” – data structures consisting of fields and methods – to design applications and programs. C++ is one of the most popular languages that supports OOP.
There are 4 pillars of OOP:
- Encapsulation
- Abstraction
- Inheritance
- Polymorphism
1️⃣ Encapsulation
Definition: Encapsulation is the practice of binding data and the methods that operate on that data into a single unit (class) and restricting direct access to some of the object’s components.
Why Encapsulation?
- It helps keep data safe from outside interference and misuse.
- Ensures better control over class data.
Key Concepts:
- Private members: Not accessible from outside the class.
- Public methods (getters/setters): Used to access and modify private members.
Example:
#include <iostream>
using namespace std;
class BankAccount {
private:
int balance; // Encapsulated (private)
public:
BankAccount() {
balance = 0;
}
void deposit(int amount) {
if (amount > 0)
balance += amount;
}
void withdraw(int amount) {
if (amount > 0 && balance >= amount)
balance -= amount;
}
int getBalance() {
return balance;
}
};
int main() {
BankAccount acc;
acc.deposit(500);
acc.withdraw(100);
cout << "Current Balance: " << acc.getBalance() << endl;
return 0;
}
Summary:
Encapsulation = Data hiding + Access control using public methods.
2️⃣ Abstraction
Definition: Abstraction means showing only the essential features of an object while hiding the unnecessary details.
Why Abstraction?
- It simplifies complex systems by breaking them into smaller, manageable parts.
- Focuses on what an object does instead of how it does it.
Key Concepts:
- Achieved through abstract classes and interfaces (pure virtual functions in C++).
- Pure virtual functions are defined using
= 0
.
Example:
#include <iostream>
using namespace std;
class Shape {
public:
virtual void draw() = 0; // Pure virtual function
};
class Circle : public Shape {
public:
void draw() override {
cout << "Drawing Circle" << endl;
}
};
class Rectangle : public Shape {
public:
void draw() override {
cout << "Drawing Rectangle" << endl;
}
};
int main() {
Shape* s1 = new Circle();
Shape* s2 = new Rectangle();
s1->draw();
s2->draw();
delete s1;
delete s2;
return 0;
}
Summary:
Abstraction = Hiding implementation + Showing interface.
3️⃣ Inheritance
Definition: Inheritance allows a class (child or derived class) to inherit properties and behavior (methods) from another class (parent or base class).
Why Inheritance?
- Promotes code reusability.
- Establishes an “is-a” relationship.
Types of Inheritance in C++:
- Single
- Multiple
- Multilevel
- Hierarchical
- Hybrid
Example (Single Inheritance):
#include <iostream>
using namespace std;
class Animal {
public:
void eat() {
cout << "This animal eats food." << endl;
}
};
class Dog : public Animal {
public:
void bark() {
cout << "The dog barks." << endl;
}
};
int main() {
Dog myDog;
myDog.eat(); // Inherited
myDog.bark(); // Own function
return 0;
}
Example (Multiple Inheritance):
class A {
public:
void showA() {
cout << "Class A" << endl;
}
};
class B {
public:
void showB() {
cout << "Class B" << endl;
}
};
class C : public A, public B {
// Inherits both A and B
};
int main() {
C obj;
obj.showA();
obj.showB();
return 0;
}
Summary:
Inheritance = Reuse existing code + Establish relationships between classes.
4️⃣ Polymorphism
Definition: Polymorphism allows functions or methods to behave differently based on the object or data they are operating on. Literally, it means “many forms”.
Why Polymorphism?
- Increases flexibility and scalability.
- Reduces code duplication.
Types of Polymorphism:
- Compile-time (Static) Polymorphism
- Function Overloading
- Operator Overloading
- Run-time (Dynamic) Polymorphism
- Function Overriding using virtual functions
Compile-time Polymorphism
Function Overloading:
class Print {
public:
void show(int i) {
cout << "Integer: " << i << endl;
}
void show(string s) {
cout << "String: " << s << endl;
}
};
int main() {
Print obj;
obj.show(100);
obj.show("Hello");
return 0;
}
Operator Overloading:
class Complex {
private:
int real, imag;
public:
Complex(int r, int i) : real(r), imag(i) {}
Complex operator + (const Complex& obj) {
return Complex(real + obj.real, imag + obj.imag);
}
void display() {
cout << real << " + " << imag << "i" << endl;
}
};
int main() {
Complex c1(3, 4), c2(1, 2);
Complex c3 = c1 + c2;
c3.display();
return 0;
}
Run-time Polymorphism
Function Overriding with Virtual Functions:
class Animal {
public:
virtual void sound() {
cout << "Some generic animal sound" << endl;
}
};
class Cat : public Animal {
public:
void sound() override {
cout << "Meow" << endl;
}
};
int main() {
Animal* a = new Cat();
a->sound(); // Calls Cat's version due to virtual function
delete a;
return 0;
}
Summary:
Polymorphism = One interface, many implementations.
The Diamond Problem in C++
What is the Diamond Problem?
The Diamond Problem is a classic ambiguity issue in multiple inheritance, where a derived class inherits from two classes that have a common base class.
Structure of the Diamond:
A
/ \
B C
\ /
D
- Class
B
and classC
inherit from classA
. - Class
D
inherits from bothB
andC
.
The Problem:
When class D
tries to access a member of class A
, C++ compiler doesn’t know whether to use the A
part of B
or the A
part of C
, because both contain their own copies of A
.
Example of the Diamond Problem
#include <iostream>
using namespace std;
class A {
public:
void display() {
cout << "Class A" << endl;
}
};
class B : public A { };
class C : public A { };
class D : public B, public C { };
int main() {
D obj;
// obj.display(); // Error: Ambiguous!
obj.B::display(); // Okay: Access through B's A
obj.C::display(); // Okay: Access through C's A
return 0;
}
Explanation:
- Class
D
has two copies of classA
(one fromB
, one fromC
). - If you call
obj.display()
, the compiler gets confused — whichdisplay()
function to call? - This is the Diamond Problem.
To explore the diamond problem and its solution in more detail, do consider reading post: The Diamond Problem in C++ Explained with Real Examples
Solution: Virtual Inheritance
C++ allows us to solve this using virtual inheritance. This tells the compiler to share a single copy of the base class when it appears multiple times in an inheritance hierarchy.
Modified Example Using Virtual Inheritance:
#include <iostream>
using namespace std;
class A {
public:
void display() {
cout << "Class A (via virtual inheritance)" << endl;
}
};
class B : virtual public A { };
class C : virtual public A { };
class D : public B, public C { };
int main() {
D obj;
obj.display(); // No ambiguity now
return 0;
}
What Changed:
- We added
virtual
beforepublic A
inB
andC
. - Now, class
D
contains only one shared instance of classA
. - No ambiguity – problem solved!
Virtual Functions vs Virtual Inheritance
These terms may sound similar, but they solve different problems:
Concept | Purpose | Usage Context |
---|---|---|
Virtual Functions | Enable dynamic (runtime) polymorphism | Function overriding |
Virtual Inheritance | Avoid multiple copies of a base class (diamond problem) | Multiple inheritance |
Virtual Functions Recap
Virtual functions ensure the correct function is called for an object, regardless of the reference type, when using base class pointers or references.
Example:
class Animal {
public:
virtual void sound() {
cout << "Generic animal sound" << endl;
}
};
class Dog : public Animal {
public:
void sound() override {
cout << "Bark" << endl;
}
};
int main() {
Animal* a = new Dog();
a->sound(); // Calls Dog's sound(), not Animal's
delete a;
return 0;
}
Why use virtual
?
Without it, a->sound()
would call Animal
’s version instead of Dog
’s.
Summary
- The Diamond Problem happens due to ambiguous inheritance paths in multiple inheritance.
- Use virtual inheritance to ensure only one instance of the base class exists.
- Virtual functions provide runtime polymorphism, letting you override functions dynamically.
- Both features are powerful tools in C++, each solving a different class of problem.
Final Thoughts
Pillar | Purpose | Achieved via |
---|---|---|
Encapsulation | Hide data, protect from misuse | Access specifiers + Getters/Setters |
Abstraction | Show essential, hide details | Abstract classes, interfaces |
Inheritance | Reuse code | Derived classes from base classes |
Polymorphism | One name, many forms | Overloading, Overriding, Virtual func |
Programming Tasks
- Encapsulation Task:
Write a classStudent
with private members:name
,rollNumber
, andGPA
. Add public methods to set and get each of these values. Include validation for GPA (should be between 0.0 and 4.0). - Abstract Shape Class:
Create an abstract classShape
with a pure virtual functionarea()
. Derive classesRectangle
andCircle
from it, and override thearea()
function appropriately. Test the behavior using base class pointers. - Polymorphism via Virtual Functions:
Design a classEmployee
with a virtual functiongetSalary()
. Derive classesManager
andEngineer
from it, each returning different salary values. Demonstrate runtime polymorphism using base class pointers. - Diamond Problem Handling:
Implement a class hierarchy that causes the diamond problem, and then resolve it using virtual inheritance. Demonstrate that there is only one instance of the base class in the final derived class.