Skip to content

Inheritance & Polymorphism

Inheritance allows you to create new classes based on existing ones, promoting code reuse. Polymorphism allows you to treat objects of different types uniformly.

Inheritance Basics

A derived class inherits members from a base class.

class Animal {
public:
    void eat() { std::cout << "Eating..."; }
};

class Dog : public Animal { // Dog inherits from Animal
public:
    using Animal::Animal; // Inherit constructors (C++11)

    void bark() { std::cout << "Woof!"; }
};

Access Control in Inheritance

  • public inheritance: public members stay public, protected stay protected. (Most common)
  • protected inheritance: public and protected become protected.
  • private inheritance: All inherited members become private. (Implementation inheritance)

Polymorphism & Virtual Functions

Polymorphism allows a function to behave differently based on the object it is called on. This is achieved using virtual functions.

class Shape {
public:
    virtual void draw() const {
        std::cout << "Drawing a generic shape.\n";
    }
};

class Circle : public Shape {
public:
    // Style Guide: Use 'override', omit 'virtual' to avoid duplication
    void draw() const override { 
        std::cout << "Drawing a circle.\n";
    }
};

void render(const Shape& s) {
    s.draw(); // Calls the correct version based on the actual object type
}

Abstract Classes & Pure Virtual Functions

An abstract class defines an interface but not necessarily an implementation. It cannot be instantiated directly.

A function becomes pure virtual by assigning = 0 to it.

class Sensor {
public:
    virtual ~Sensor() = default; // Always virtual destructor!
    virtual double read_value() = 0; // Pure virtual
};

class TempSensor : public Sensor {
public:
    double read_value() override {
        return 25.5; // Must implement this
    }
};

Virtual Destructors

Crucial Rule: If a class is intended to be used as a base class, its destructor must be virtual.

If not, deleting a derived object through a base pointer results in undefined behavior (usually a resource leak because the derived destructor is not called).

class Base {
public:
    virtual ~Base() { std::cout << "Base destroyed\n"; }
};

class Derived : public Base {
public:
    ~Derived() { std::cout << "Derived destroyed\n"; }
};

Base* obj = new Derived();
delete obj; // Correctly calls ~Derived() then ~Base()

The final Keyword

You can prevent a class from being inherited or a function from being overridden.

class FinalClass final { };
// class Derived : public FinalClass {}; // Error

class Base {
    virtual void foo();
};

class Derived : public Base {
    void foo() override final; // Cannot be overridden further
};