Object-Oriented Programming

Inheritance

Master C++ inheritance — base and derived classes, access control, virtual functions, abstract classes, multiple inheritance, and the Liskov Substitution Principle.

Inheritance

Inheritance lets you build a new class (derived) on top of an existing one (base). The derived class inherits all non-private members of the base, and can add new members or override existing behaviour. Inheritance models the "is-a" relationship: a Dog is an Animal, a SavingsAccount is a BankAccount.


Basic Inheritance

cpp
#include <iostream>
#include <string>
using namespace std;

// Base class
class Animal {
protected:           // Protected — accessible in derived classes
    string name;
    int    age;

public:
    Animal(const string& name, int age) : name(name), age(age) {}

    void breathe() const {
        cout << name << " breathes\n";
    }

    void eat() const {
        cout << name << " eats\n";
    }

    string getName() const { return name; }
};

// Derived class — inherits Animal publicly
class Dog : public Animal {
    string breed;

public:
    // Call base constructor via initialiser list
    Dog(const string& name, int age, const string& breed)
        : Animal(name, age), breed(breed) {}

    void bark() const {
        cout << name << " barks: Woof!\n";
    }

    void info() const {
        cout << name << " (" << breed << "), age " << age << "\n";
    }
};

class Cat : public Animal {
public:
    Cat(const string& name, int age) : Animal(name, age) {}

    void meow() const {
        cout << name << " meows: Meow!\n";
    }
};

int main() {
    Dog dog("Rex", 3, "German Shepherd");
    dog.breathe();   // Inherited from Animal
    dog.eat();       // Inherited from Animal
    dog.bark();      // Dog-specific
    dog.info();      // Dog-specific

    Cat cat("Whiskers", 5);
    cat.breathe();   // Inherited
    cat.meow();      // Cat-specific

    return 0;
}
// Rex breathes
// Rex eats
// Rex barks: Woof!
// Rex (German Shepherd), age 3
// Whiskers breathes
// Whiskers meows: Meow!

Inheritance Access Specifiers

Base memberpublic inheritanceprotected inheritanceprivate inheritance
publicpublicprotectedprivate
protectedprotectedprotectedprivate
privateInaccessibleInaccessibleInaccessible

Use public inheritance for "is-a" relationships. Use private inheritance for "implemented-in-terms-of" (composition via inheritance — rarely needed).


Virtual Functions and Overriding

cpp
#include <iostream>
#include <string>
using namespace std;

class Shape {
public:
    virtual double area() const {      // Virtual — can be overridden
        return 0.0;
    }

    virtual string name() const {
        return "Shape";
    }

    virtual void describe() const {   // Non-pure virtual — has default implementation
        cout << name() << " with area " << area() << "\n";
    }

    virtual ~Shape() {}               // Virtual destructor — essential for polymorphism!
};

class Circle : public Shape {
    double radius;
public:
    Circle(double r) : radius(r) {}

    double area() const override {    // 'override' — compiler checks base has virtual
        return 3.14159265358979 * radius * radius;
    }
    string name() const override { return "Circle(r=" + to_string(radius) + ")"; }
};

class Rectangle : public Shape {
    double width, height;
public:
    Rectangle(double w, double h) : width(w), height(h) {}

    double area() const override { return width * height; }
    string name() const override { return "Rectangle"; }
};

class Triangle : public Shape {
    double base, height;
public:
    Triangle(double b, double h) : base(b), height(h) {}

    double area() const override { return 0.5 * base * height; }
    string name() const override { return "Triangle"; }
};

int main() {
    Circle    c(5.0);
    Rectangle r(4.0, 6.0);
    Triangle  t(3.0, 8.0);

    // Polymorphism via base class pointer
    Shape* shapes[] = {&c, &r, &t};
    for (Shape* s : shapes) {
        s->describe();
    }
    // Circle(r=5.000000) with area 78.5398
    // Rectangle with area 24
    // Triangle with area 12

    return 0;
}

Warning: Always declare a virtual destructor in base classes that will be used polymorphically. Without it, deleting a derived object through a base pointer causes undefined behaviour (partial destruction).


Abstract Classes and Pure Virtual Functions

A pure virtual function (declared with = 0) has no implementation in the base class. A class with at least one pure virtual function is abstract — you cannot instantiate it directly:

cpp
#include <iostream>
#include <string>
using namespace std;

class Logger {
public:
    // Pure virtual — subclasses MUST implement this
    virtual void log(const string& message) = 0;
    virtual void flush() = 0;

    // Non-pure virtual — provides default
    virtual void logError(const string& msg) {
        log("[ERROR] " + msg);
    }

    virtual ~Logger() {}
};

class ConsoleLogger : public Logger {
public:
    void log(const string& message) override {
        cout << message << "\n";
    }
    void flush() override { /* cout doesn't need flushing manually */ }
};

class FileLogger : public Logger {
    string filename;
    vector<string> buffer;
public:
    FileLogger(const string& fn) : filename(fn) {}
    void log(const string& message) override {
        buffer.push_back(message);
    }
    void flush() override {
        cout << "Writing " << buffer.size() << " lines to " << filename << "\n";
        buffer.clear();
    }
};

void process(Logger& logger) {
    logger.log("Application started");
    logger.logError("Config file missing");
    logger.flush();
}

int main() {
    // Logger logger;  // ERROR — abstract class

    ConsoleLogger clog;
    FileLogger    flog("app.log");

    process(clog);
    process(flog);
    return 0;
}

Calling Base Class Functions

cpp
#include <iostream>
using namespace std;

class Vehicle {
protected:
    string brand;
    int    speed;

public:
    Vehicle(const string& brand, int speed) : brand(brand), speed(speed) {}

    virtual void describe() const {
        cout << "Vehicle: " << brand << ", max speed " << speed << " km/h\n";
    }
};

class Car : public Vehicle {
    int doors;

public:
    Car(const string& brand, int speed, int doors)
        : Vehicle(brand, speed), doors(doors) {}

    void describe() const override {
        Vehicle::describe();                   // Call base version first
        cout << "  Doors: " << doors << "\n"; // Add derived-specific info
    }
};

int main() {
    Car car("Toyota", 180, 4);
    car.describe();
    // Vehicle: Toyota, max speed 180 km/h
    //   Doors: 4
    return 0;
}

Multiple Inheritance

C++ allows inheriting from more than one base class:

cpp
#include <iostream>
using namespace std;

class Flyable {
public:
    virtual void fly() { cout << "Flying\n"; }
    virtual ~Flyable() {}
};

class Swimmable {
public:
    virtual void swim() { cout << "Swimming\n"; }
    virtual ~Swimmable() {}
};

class Duck : public Flyable, public Swimmable {
public:
    void fly()  override { cout << "Duck flies\n"; }
    void swim() override { cout << "Duck swims\n"; }
    void quack()        { cout << "Quack!\n"; }
};

int main() {
    Duck d;
    d.fly();    // Duck flies
    d.swim();   // Duck swims
    d.quack();  // Quack!

    Flyable*   fb = &d;  // Pointer to Flyable interface
    Swimmable* sb = &d;  // Pointer to Swimmable interface
    fb->fly();            // Duck flies (virtual dispatch)
    sb->swim();           // Duck swims
    return 0;
}

Note: Multiple inheritance with data (member variables) in both bases can lead to the diamond problem. Use virtual inheritance (class A : virtual public Base) to resolve it, or redesign using composition.


The final Keyword (C++11)

cpp
class Base { virtual void foo(); };

class MidLevel : public Base {
    void foo() override final;  // Subclasses cannot override foo anymore
};

class Leaf final : public MidLevel {
    // No class can inherit from Leaf
};

// class Extended : public Leaf {};  // ERROR — Leaf is final

Practical Example: Payment System

cpp
#include <iostream>
#include <string>
#include <vector>
#include <memory>
using namespace std;

class Payment {
protected:
    string currency;
    double amount;

public:
    Payment(double amount, const string& currency = "INR")
        : amount(amount), currency(currency) {}

    virtual bool   process()      = 0;    // Must implement
    virtual string methodName()   = 0;
    virtual string receipt() const { return ""; }
    virtual ~Payment() {}

    double getAmount()          const { return amount; }
    const string& getCurrency() const { return currency; }
};

class UPIPayment : public Payment {
    string upiId;

public:
    UPIPayment(double amount, const string& upiId)
        : Payment(amount), upiId(upiId) {}

    bool process() override {
        cout << "Processing UPI payment of " << currency << " " << amount
             << " to " << upiId << "\n";
        return true;   // Simulate success
    }

    string methodName() override { return "UPI"; }
    string receipt() const override {
        return "UPI-" + upiId + "-" + to_string(amount);
    }
};

class CardPayment : public Payment {
    string cardNumber;
    string cardHolder;

public:
    CardPayment(double amount, const string& card, const string& holder)
        : Payment(amount), cardNumber(card), cardHolder(holder) {}

    bool process() override {
        string masked = "****-****-****-" + cardNumber.substr(cardNumber.size() - 4);
        cout << "Processing card payment of " << currency << " " << amount
             << " for " << cardHolder << " (" << masked << ")\n";
        return true;
    }

    string methodName() override { return "Card"; }
};

class PaymentProcessor {
public:
    static void processAll(vector<unique_ptr<Payment>>& payments) {
        double total = 0;
        int success = 0;

        for (auto& p : payments) {
            if (p->process()) {
                total += p->getAmount();
                success++;
            }
        }
        cout << "\n--- Summary ---\n";
        cout << success << "/" << payments.size() << " payments processed\n";
        cout << "Total: INR " << total << "\n";
    }
};

int main() {
    vector<unique_ptr<Payment>> payments;
    payments.push_back(make_unique<UPIPayment>(1500.0, "alice@paytm"));
    payments.push_back(make_unique<CardPayment>(3200.0, "4111111111111111", "Bob"));
    payments.push_back(make_unique<UPIPayment>(800.0, "charlie@gpay"));

    PaymentProcessor::processAll(payments);
    // Processing UPI payment of INR 1500 to alice@paytm
    // Processing card payment of INR 3200 for Bob (****-****-****-1111)
    // Processing UPI payment of INR 800 to charlie@gpay
    //
    // --- Summary ---
    // 3/3 payments processed
    // Total: INR 5500

    return 0;
}

Summary

  • Use public inheritance for "is-a" relationships; the derived class gets all public and protected members
  • virtual functions enable runtime polymorphism — the correct derived function is called through a base pointer
  • Always declare a virtual destructor in base classes used polymorphically — prevents partial destruction bugs
  • Pure virtual functions (= 0) make a class abstract — it cannot be instantiated, only subclassed
  • Use override on every overriding function — the compiler verifies the base class actually has a matching virtual
  • Use final on a class or method to prevent further inheritance or overriding
  • Multiple inheritance works but can lead to diamond problems — prefer composition or virtual inheritance

Next, we'll explore polymorphism — the mechanism behind virtual function dispatch and the vtable.