Skip to content
Home » Object-Oriented Programming in C++ – The 4 Pillars Explained

Object-Oriented Programming in C++ – The 4 Pillars Explained

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:

  1. Encapsulation
  2. Abstraction
  3. Inheritance
  4. 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 class C inherit from class A.
  • Class D inherits from both B and C.

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 class A (one from B, one from C).
  • If you call obj.display(), the compiler gets confused — which display() function to call?
  • This is the Diamond Problem.

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 before public A in B and C.
  • Now, class D contains only one shared instance of class A.
  • No ambiguity – problem solved!

Virtual Functions vs Virtual Inheritance

These terms may sound similar, but they solve different problems:

ConceptPurposeUsage Context
Virtual FunctionsEnable dynamic (runtime) polymorphismFunction overriding
Virtual InheritanceAvoid 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

PillarPurposeAchieved via
EncapsulationHide data, protect from misuseAccess specifiers + Getters/Setters
AbstractionShow essential, hide detailsAbstract classes, interfaces
InheritanceReuse codeDerived classes from base classes
PolymorphismOne name, many formsOverloading, Overriding, Virtual func

Programming Tasks

  1. Encapsulation Task:
    Write a class Student with private members: name, rollNumber, and GPA. Add public methods to set and get each of these values. Include validation for GPA (should be between 0.0 and 4.0).
  2. Abstract Shape Class:
    Create an abstract class Shape with a pure virtual function area(). Derive classes Rectangle and Circle from it, and override the area() function appropriately. Test the behavior using base class pointers.
  3. Polymorphism via Virtual Functions:
    Design a class Employee with a virtual function getSalary(). Derive classes Manager and Engineer from it, each returning different salary values. Demonstrate runtime polymorphism using base class pointers.
  4. 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.

4 thoughts on “Object-Oriented Programming in C++ – The 4 Pillars Explained”

  1. Thanks for the recommendations you have provided here. Another thing I would like to mention is that computer system memory needs generally rise along with other innovations in the technology. For instance, whenever new generations of processor chips are made in the market, there is usually a matching increase in the type demands of all laptop or computer memory in addition to hard drive room. This is because the program operated by simply these processor chips will inevitably rise in power to use the new technological innovation.

  2. Whats up are using WordPress for your blog platform? I’m new to the blog world but I’m trying to get started and create my own. Do you need any html coding knowledge to make your own blog? Any help would be greatly appreciated!

Leave a Reply

Your email address will not be published. Required fields are marked *