Skip to content
Inheritance and Virtual Functions
step 1/7

Reading — step 1 of 7

Learn

~3 min readClasses, Inheritance, and Templates

Inheritance lets one class extend another, sharing implementation and creating an is-a relationship. Combined with virtual functions, it powers runtime polymorphism — the foundation of OOP in C++.

Basic inheritance

class Animal {
protected:
    std::string name;
public:
    Animal(std::string n) : name{std::move(n)} {}
    
    void describe() const {
        std::cout << "I am " << name << "\n";
    }
};

class Dog : public Animal {
public:
    Dog(std::string n) : Animal{std::move(n)} {}
    
    void bark() const { std::cout << "Woof!\n"; }
};

Dog d{"Rex"};
d.describe();    // I am Rex (inherited from Animal)
d.bark();        // Woof!

class Dog : public Animal — Dog inherits from Animal publicly. Always use public inheritance for is-a relationships. Private/protected inheritance is rare and means "implemented in terms of" — usually composition is better.

Calling the base constructor

Use the member initializer list:

Dog(std::string n) : Animal{std::move(n)} {}

Must come BEFORE any of Dog's own member initializers.

Virtual functions — runtime polymorphism

Without virtual, calling a method through a base pointer/reference uses the base class's version (static dispatch):

class Shape {
public:
    double area() const { return 0; }       // NOT virtual
};

class Circle : public Shape {
    double r;
public:
    Circle(double r_) : r{r_} {}
    double area() const { return 3.14159 * r * r; }
};

Shape* s = new Circle(5);
std::cout << s->area();    // 0 — not what you wanted!

Mark the base method virtual for dynamic dispatch:

class Shape {
public:
    virtual double area() const { return 0; }
    virtual ~Shape() = default;    // ALWAYS virtual destructor in polymorphic bases
};

class Circle : public Shape {
    double r;
public:
    Circle(double r_) : r{r_} {}
    double area() const override { return 3.14159 * r * r; }
};

Shape* s = new Circle(5);
std::cout << s->area();    // 78.54 — polymorphic dispatch
delete s;

override — say what you mean

Mark overriding methods with override. The compiler will error if the base method doesn't actually exist as virtual:

double area() const override { return 3.14159 * r * r; }

Without override, a typo in the signature silently creates a NEW method (hiding instead of overriding). Always use override — it's a free correctness check.

Pure virtual and abstract classes

virtual ... = 0 declares a method WITHOUT an implementation:

class Shape {
public:
    virtual double area() const = 0;     // pure virtual
    virtual ~Shape() = default;
};

A class with at least one pure virtual is abstract — can't be instantiated directly. Subclasses must implement the pure virtuals or remain abstract themselves. This is C++'s equivalent of an interface.

Virtual destructors — CRITICAL

Shape* s = new Circle(5);
delete s;     // calls Shape's destructor only — Circle's destructor is SKIPPED!

If ~Shape isn't virtual, deleting a derived object through a base pointer leaks resources. Rule: if a class has any virtual function, give it a virtual destructor.

Common mistakes

  • Forgetting virtual — gets you static dispatch, which is the wrong default for polymorphic types.
  • Forgetting override — typos silently create new methods. Always annotate.
  • No virtual destructor in a polymorphic base — leaks when deleting through a base pointer.
  • Slicing: Animal a = dog; copies only the Animal portion of dog. Use Animal& or Animal* for polymorphism.
  • Multiple inheritance with shared base classes — diamond problem. Use virtual inheritance carefully or restructure.

Discussion

Ask a question, share an insight, or help someone who’s stuck.

Sign in to post a comment or reply.

Loading…