Inheritance & Polymorphism

Inheritance lets one class build on another, picking up its data and behavior and adding or changing only what's different. Polymorphism goes a step further: it lets code written against a base class work correctly with any derived class, without knowing in advance which one it's actually dealing with.

A base class and a derived class

: public Base after a class name means "inherits from Base." The derived class automatically gets everything the base class has, and can add its own members on top:

</> inheritance.cpp
#include <iostream>
#include <string>

class Animal {
public:
    std::string name;

    Animal(std::string animalName) {
        name = animalName;
    }

    void eat() {
        std::cout << name << " is eating." << std::endl;
    }
};

class Dog : public Animal {
public:
    Dog(std::string dogName) : Animal(dogName) {}

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

int main() {
    Dog rex("Rex");
    rex.eat();
    rex.bark();
    return 0;
}
Terminal output
Rex is eating.
Rex says Woof!

Dog never defines eat() or name itself — it inherits both from Animal. Dog(std::string dogName) : Animal(dogName) {} is the derived constructor calling the base class's constructor to set up the inherited name member, then adding nothing further of its own before the (empty) body runs.

Overriding with virtual functions

Marking a base class method virtual lets a derived class replace it with its own version using override. Without virtual, C++ has no way to know a derived class's version should be used instead of the base's:

</> override.cpp
#include <iostream>
#include <string>

class Animal {
public:
    std::string name;
    Animal(std::string animalName) { name = animalName; }

    virtual void speak() {
        std::cout << name << " makes a sound." << std::endl;
    }
};

class Cat : public Animal {
public:
    Cat(std::string catName) : Animal(catName) {}

    void speak() override {
        std::cout << name << " says Meow!" << std::endl;
    }
};

int main() {
    Animal generic("Some animal");
    Cat whiskers("Whiskers");

    generic.speak();
    whiskers.speak();
    return 0;
}
Terminal output
Some animal makes a sound.
Whiskers says Meow!

Cat::speak() completely replaces Animal::speak() for Cat objects. override isn't strictly required, but it's worth always including — the compiler checks that you're actually overriding a real virtual function, and catches a typo'd method name that would otherwise silently create an unrelated new method instead of overriding anything.

Polymorphism: one interface, many behaviors

This is where virtual pays off: a function can accept a base-class reference and call an overridden method on it, and the derived class's version runs — even though the function itself only ever mentions the base class:

</> polymorphism.cpp
#include <iostream>
#include <string>
#include <vector>

class Animal {
public:
    std::string name;
    Animal(std::string animalName) { name = animalName; }
    virtual void speak() {
        std::cout << name << " makes a sound." << std::endl;
    }
};

class Dog : public Animal {
public:
    Dog(std::string n) : Animal(n) {}
    void speak() override { std::cout << name << " says Woof!" << std::endl; }
};

class Cat : public Animal {
public:
    Cat(std::string n) : Animal(n) {}
    void speak() override { std::cout << name << " says Meow!" << std::endl; }
};

void makeItSpeak(Animal& a) {
    a.speak();
}

int main() {
    Dog rex("Rex");
    Cat whiskers("Whiskers");

    makeItSpeak(rex);
    makeItSpeak(whiskers);
    return 0;
}
Terminal output
Rex says Woof!
Whiskers says Meow!

makeItSpeak(Animal& a) is written entirely in terms of Animal and knows nothing about Dog or Cat specifically. Because speak() is virtual, the call inside it resolves at runtime to whichever derived class's version actually applies to the object passed in — that's polymorphism: one function, many behaviors, decided by the real type of the object rather than the type written in the code.

Note: if speak() in Animal were not marked virtual, makeItSpeak would print "makes a sound." for every animal, regardless of its actual type — this is called "slicing" or, more precisely here, static (compile-time) dispatch. Forgetting virtual on a method you intend to override is one of the most common sources of surprising bugs when C++ programmers first work with inheritance.
Course complete: that covers the full C++ course — variables and data types, operators, conditionals, loops, functions and pass-by-reference, arrays and std::vector, std::string, pointers and references, and finally classes, constructors, destructors, inheritance, and polymorphism. You now have the core toolkit C++ programs are built from — from here, the natural next steps are the wider standard library (algorithms, more containers) and modern memory-management idioms like smart pointers.