Introduction

  • Abstraction is one of the four pillars of OOP (alongside Encapsulation, Inheritance, Polymorphism).
  • It means hiding complex implementation details and exposing only what’s necessary.
  • The user of a class/function only needs to know what it does, not how it does it.
  • In C++, abstraction is achieved via abstract classes, interfaces (all-pure-virtual classes), and access modifiers.

Real-World Analogy

  • A car — you use the steering wheel, pedals, and gear shift. You don’t need to know how the engine combustion works internally.
  • A TV remote — press a button, channel changes. The IR signal encoding is hidden.

Advantages

  • Reduces complexity — users interact with a simple interface.
  • Improves maintainability — internal changes don’t affect users.
  • Enables code reuse and polymorphism.
  • Enforces separation of concerns.

Disadvantages

  • Over-abstraction can make code harder to trace and debug.
  • Adds indirection — slight performance overhead (vtable).
  • Requires careful design upfront.

Types of Abstraction

Data Abstraction

  • Hiding internal data representation — expose only through controlled methods.
  • Achieved via private/protected members + public getters/setters.
class BankAccount {
private:
    double balance;  // hidden — user can't access directly
 
public:
    BankAccount(double initial) : balance(initial) {}
 
    void deposit(double amount) {
        if (amount > 0) balance += amount;
    }
 
    void withdraw(double amount) {
        if (amount > 0 && amount <= balance) balance -= amount;
    }
 
    double getBalance() const { return balance; }
};
 
BankAccount acc(1000.0);
acc.deposit(500);
acc.withdraw(200);
std::cout << acc.getBalance(); // 1300
// acc.balance = 99999; // ERROR — private

Procedural Abstraction

  • Hiding the steps of an algorithm behind a function name.
  • Caller only knows the function signature, not the internal logic.
// User calls sort() — doesn't need to know it's introsort internally
#include <algorithm>
#include <vector>
 
std::vector<int> v = {5, 2, 8, 1, 9};
std::sort(v.begin(), v.end());
// [1, 2, 5, 8, 9] — how it sorted is abstracted away

Abstract Class Abstraction

  • Define a common interface via pure virtual functions.
  • Each subclass provides its own implementation.
class Logger {
public:
    virtual void log(const std::string& msg) = 0;
    virtual ~Logger() {}
};
 
class ConsoleLogger : public Logger {
public:
    void log(const std::string& msg) override {
        std::cout << "[Console] " << msg << "\n";
    }
};
 
class FileLogger : public Logger {
public:
    void log(const std::string& msg) override {
        // write to file — implementation hidden
        std::cout << "[File] " << msg << "\n";
    }
};
 
// User only knows about Logger interface
void process(Logger& logger) {
    logger.log("Processing started");
    logger.log("Processing done");
}
 
ConsoleLogger cl;
process(cl);
// [Console] Processing started
// [Console] Processing done

C++ Examples

Shape Abstraction

#include <iostream>
#include <cmath>
 
class Shape {
public:
    virtual double area() = 0;
    virtual double perimeter() = 0;
    virtual void describe() {
        std::cout << "Area: " << area()
                  << ", Perimeter: " << perimeter() << "\n";
    }
    virtual ~Shape() {}
};
 
class Circle : public Shape {
    double r;
public:
    Circle(double r) : r(r) {}
    double area() override { return M_PI * r * r; }
    double perimeter() override { return 2 * M_PI * r; }
};
 
class Square : public Shape {
    double side;
public:
    Square(double s) : side(s) {}
    double area() override { return side * side; }
    double perimeter() override { return 4 * side; }
};
 
int main() {
    Shape* c = new Circle(5);
    Shape* s = new Square(4);
 
    c->describe(); // Area: 78.539..., Perimeter: 31.415...
    s->describe(); // Area: 16, Perimeter: 16
 
    delete c; delete s;
}

Payment System Abstraction

class PaymentProcessor {
public:
    virtual bool processPayment(double amount) = 0;
    virtual std::string getProviderName() = 0;
    virtual ~PaymentProcessor() {}
};
 
class StripeProcessor : public PaymentProcessor {
public:
    bool processPayment(double amount) override {
        std::cout << "Stripe: charging $" << amount << "\n";
        return true; // internal Stripe API calls hidden
    }
    std::string getProviderName() override { return "Stripe"; }
};
 
class PayPalProcessor : public PaymentProcessor {
public:
    bool processPayment(double amount) override {
        std::cout << "PayPal: charging $" << amount << "\n";
        return true;
    }
    std::string getProviderName() override { return "PayPal"; }
};
 
void checkout(PaymentProcessor& processor, double total) {
    if (processor.processPayment(total))
        std::cout << "Payment via " << processor.getProviderName() << " successful\n";
}
 
StripeProcessor stripe;
checkout(stripe, 99.99);
// Stripe: charging $99.99
// Payment via Stripe successful

Abstraction vs Encapsulation

  • These two are related but distinct concepts.
Concept         What it does                          How in C++
Abstraction     Hides complexity, shows interface      Abstract classes, pure virtual
Encapsulation   Bundles data + methods, restricts      private/protected members
                direct access to internal state
  • Abstraction = design-level concept (what to expose).
  • Encapsulation = implementation-level concept (how to protect data).

Key Takeaways

  • Abstraction = show what, hide how.
  • Achieved in C++ via abstract classes (pure virtual), access modifiers, and well-designed APIs.
  • Reduces coupling — callers depend on interfaces, not implementations.
  • Combine with encapsulation for clean, maintainable OOP design.