Skip to main content
    Courses/C++/Inheritance & Polymorphism

    Lesson 10 • Intermediate

    Inheritance & Polymorphism

    By the end of this lesson you'll be able to build a class hierarchy with base and derived classes, call base constructors correctly, override behaviour with virtual functions, and call one method on many object types through a base pointer — the heart of flexible, extensible C++.

    What You'll Learn

    • Write a derived class that inherits from a base class with public inheritance
    • Call the base class constructor from a derived constructor's init list
    • Mark methods virtual and replace them in derived classes with override
    • Use base pointers/references for runtime polymorphism (dynamic dispatch)
    • Create abstract classes with pure virtual functions ( = 0 )
    • Add a virtual destructor so deleting through a base pointer is safe

    💡 Real-World Analogy

    Think of inheritance like biology. Animal is the general category — every animal can eat and sleep. A Dog is an Animal, so it gets all those traits for free, and adds its own (barking). That "is-a" link is exactly what class Dog : public Animal means. Polymorphism is the next idea: if you ask a line-up of different animals to "speak", each makes its own sound — same instruction, different result. In C++ the virtual keyword is what makes that happen, deciding the right behaviour while the program runs.

    🔐 Member Access Under Inheritance

    AccessBase classDerived classOutside
    public
    protected
    private

    Use protected for data a derived class needs to touch but the outside world shouldn't. private base members still exist in the derived object — they're just only reachable through the base's own public/protected methods.

    1. Base & Derived Classes

    A base class holds what's shared; a derived class adds what's special. You write class Derived : public Base — the public keyword gives you public inheritance, the normal "Derived is-a Base" relationship. The derived object contains the base part, so it can use the base's public and protected members as if they were its own. Read this worked example and run it.

    Worked example: Animal → Dog

    Read every comment, run it, and check the output matches.

    Try it Yourself »
    C++
    #include <iostream>
    #include <string>
    using namespace std;
    
    // BASE class — the shared, general thing.
    class Animal {
    protected:                 // 'protected' = visible to derived classes, hidden outside
        string name;
    public:
        // Base constructor: sets up the part every Animal shares.
        Animal(string n) : name(n) {
            cout << "Animal ctor: " << name << endl;
        }
        void eat() const {
            cout << name << " is eating." << endl;
        }
    };
    
    // DERIVED class — 'public' inheritance
    ...

    Notice the constructor: Dog(string n, string b) : Animal(n), breed(b). The base part must be built first, so you call the base constructor in the init list. If you don't, C++ tries to call Animal() with no arguments — and since Animal has no such constructor, that's a compile error. Now your turn: finish the Car class below.

    🎯 Your turn: write a derived class

    Fill in the two ___ blanks to inherit and call the base constructor.

    Try it Yourself »
    C++
    #include <iostream>
    #include <string>
    using namespace std;
    
    class Vehicle {
    protected:
        string brand;
    public:
        Vehicle(string b) : brand(b) {}
        void honk() const { cout << brand << ": Beep beep!" << endl; }
    };
    
    // 🎯 YOUR TURN — make Car inherit from Vehicle.
    
    // 1) Inherit publicly from Vehicle
    class Car : public ___ {        // 👉 put the base class name here
        int doors;
    public:
        // 2) Pass 'b' up to the Vehicle constructor, store 'd' in doors
        Car(string b, int d) : ___(b), 
    ...

    2. Virtual Functions, override & Polymorphism

    Polymorphism means "many forms": you call one method through a base pointer and get the right derived behaviour. The magic word is virtual. Mark a base method virtual to say "a child may replace me", then in the child write the same method with override. Because the choice is made while the program runs, this is called runtime polymorphism (or dynamic dispatch). The key rule: it only works through a base pointer or reference, never a plain value.

    Worked example: one call, many behaviours

    A vector of Animal* holds Dogs and Cats — speak() does the right thing.

    Try it Yourself »
    C++
    #include <iostream>
    #include <string>
    #include <vector>
    using namespace std;
    
    class Animal {
    protected:
        string name;
    public:
        Animal(string n) : name(n) {}
    
        // 'virtual' lets a derived class REPLACE this through a base pointer.
        virtual void speak() const {
            cout << name << " makes a sound." << endl;
        }
    
        // VIRTUAL DESTRUCTOR — without it, 'delete base;' on a derived
        // object would not run the derived destructor. Always add it.
        virtual ~Animal() = default;
    };
    ...

    See how a->speak() runs Dog::speak for the dog and Cat::speak for the cat, even though a is an Animal*? That's dynamic dispatch. Now add the virtual and override keywords yourself:

    🎯 Your turn: make it overridable

    Add the keyword that makes a base method overridable, and the one that replaces it.

    Try it Yourself »
    C++
    #include <iostream>
    #include <string>
    #include <vector>
    using namespace std;
    
    class Employee {
    protected:
        string name;
    public:
        Employee(string n) : name(n) {}
        // 1) Make this overridable so derived classes can replace it
        ___ double pay() const {        // 👉 add the keyword that means "overridable"
            return 2000;                // base pay
        }
        virtual ~Employee() = default;
    };
    
    class Manager : public Employee {
    public:
        Manager(string n) : Employee(n) {}
        // 2) 
    ...

    3. Abstract Classes & Pure Virtual Functions

    Sometimes the base class has no sensible default — what's the area of a generic "Shape"? You make the function pure virtual by writing = 0: virtual double area() const = 0;. That has two effects. First, every derived class must implement it. Second, the base becomes an abstract class — you can't create one directly, only its concrete children. Abstract classes are how C++ defines an interface.

    Worked example: an abstract Shape

    Shape can't be instantiated — but a Square can, and report() calls its area().

    Try it Yourself »
    C++
    #include <iostream>
    #include <string>
    using namespace std;
    
    // ABSTRACT class: it has a PURE virtual function ( = 0 ).
    // You CANNOT create a Shape directly — only classes that
    // provide every pure virtual can be instantiated.
    class Shape {
    public:
        // '= 0' makes this PURE virtual: derived classes MUST define it.
        virtual double area() const = 0;
    
        // A virtual destructor is still required for safe deletion.
        virtual ~Shape() = default;
    
        // A normal (non-pure) function the chil
    ...

    Deep Dive: the Virtual Destructor Rule

    When you delete an object through a base pointer, C++ needs to know which destructor to run. If the base destructor is not virtual, only the base part is destroyed — the derived part leaks, and the standard calls this undefined behaviour.

    The rule is simple: any class with virtual functions should declare a virtual destructor. Often virtual ~Base() = default; is all you need.

    class Base {
    public:
        virtual ~Base() = default;   // ✅ makes 'delete base;' safe
    };
    class Derived : public Base { /* ... */ };
    
    Base* p = new Derived();
    delete p;   // runs ~Derived() then ~Base() — correct cleanup

    Pro Tips

    • 💡 Always write override: it's free insurance — the compiler rejects a mismatched signature instead of silently making a new method.
    • 💡 Prefer composition over inheritance: a "has-a" link (a Car has an Engine) is often cleaner than deep "is-a" hierarchies.
    • 💡 Use abstract classes as interfaces: a class with only pure virtual functions plays the role of a Java/C# interface.
    • 💡 Pointer or reference, never value: polymorphism is lost the moment you copy a derived object into a base value.

    Common Errors (and the fix)

    • Missing virtual destructor: deleting a derived object through a base pointer with a non-virtual destructor is undefined behaviour and leaks memory. Add virtual ~Base() = default;.
    • Object slicing: Animal a = myDog; copies only the Animal part — the override and extra data are sliced off. Use Animal& or Animal* to keep polymorphism.
    • Forgetting override (or a signature typo): void speak() in the child when the base is void speak() const creates a new function — the base version still runs. Add override and the compiler catches it.
    • Calling a virtual in a constructor: the derived part isn't built yet, so the base version runs, not your override. Do that work after construction.
    • "cannot declare variable to be of abstract type": you tried to instantiate a class that still has an unimplemented pure virtual. Implement every = 0 function, or use a pointer to a concrete child.
    • "no matching function ... Base()": the base has no default constructor, so you must call it explicitly in the init list — Derived(...) : Base(arg) { }.

    📋 Quick Reference

    GoalCode
    Inherit publiclyclass Dog : public Animal { };
    Call base constructorDog(string n) : Animal(n) { }
    Make overridablevirtual void speak() const;
    Override in childvoid speak() const override;
    Pure virtual (abstract)virtual double area() const = 0;
    Virtual destructorvirtual ~Animal() = default;
    Polymorphic callAnimal* a = new Dog(); a->speak();

    Frequently Asked Questions

    Q: What is the difference between virtual and override in C++?

    You write 'virtual' in the BASE class to say a function may be replaced by a derived class. You write 'override' in the DERIVED class to say this function replaces a base virtual. 'override' is optional but always recommended: the compiler checks a matching base virtual exists, so a typo in the signature becomes a compile error instead of a silently new function.

    Q: Why does my C++ class need a virtual destructor?

    If you ever delete a derived object through a base-class pointer (Base* p = new Derived(); delete p;), the destructor must be virtual. Without it, only ~Base() runs and the derived part is never cleaned up — that is undefined behaviour and a common memory leak. Rule of thumb: any class with virtual functions should have a virtual destructor.

    Q: What is object slicing in C++?

    Slicing happens when you copy a derived object into a base VALUE (Base b = derived;). Only the base part is copied; the derived data and overrides are 'sliced off', so virtual calls run the base version. To keep polymorphism, use a base pointer or reference (Base* or Base&), never a base value.

    Q: What is a pure virtual function and an abstract class?

    A pure virtual function is declared with '= 0' and has no base implementation: virtual double area() const = 0;. A class containing at least one pure virtual is abstract — you cannot create an instance of it, only of derived classes that implement every pure virtual. Abstract classes are how C++ expresses interfaces.

    Q: Why shouldn't I call a virtual function in a constructor?

    During a base constructor, the derived part of the object does not exist yet, so C++ calls the BASE version of any virtual — not the override you expected. The same applies in destructors. Avoid relying on virtual dispatch in constructors and destructors; do that work after the object is fully built.

    Mini-Challenge: Shape Hierarchy

    No blanks this time — just a brief and an outline. Build an abstract Shape with a pure virtual area(), derive Circle and Rectangle, then loop over a vector<Shape*> and print each area. Check your output against the comments.

    🎯 Mini-Challenge: Shape → Circle / Rectangle

    Write the hierarchy yourself and print each shape's area through a base pointer.

    Try it Yourself »
    C++
    #include <iostream>
    using namespace std;
    
    int main() {
        // 🎯 MINI-CHALLENGE: a tiny shape hierarchy
        //
        // 1. Make an abstract base class Shape with:
        //       virtual double area() const = 0;   // pure virtual
        //       virtual ~Shape() = default;        // virtual destructor
        //
        // 2. class Circle : public Shape — store a radius;
        //       area() returns 3.14159 * radius * radius
        //
        // 3. class Rectangle : public Shape — store width & height;
        //       are
    ...

    🎉 Lesson Complete

    • class Derived : public Base models an "is-a" relationship
    • ✅ Call the base constructor in the init list: Derived(...) : Base(...)
    • virtual in the base + override in the child = runtime polymorphism
    • ✅ Polymorphism needs a base pointer or reference — a value slices
    • = 0 makes a function pure virtual and its class abstract
    • ✅ Give every polymorphic base a virtual destructor
    • Next lesson: Templates — write generic code that works with any type

    Sign up for free to track which lessons you've completed and get learning reminders.

    Previous

    Cookie & Privacy Settings

    We use cookies to improve your experience, analyze traffic, and show personalized ads. You can manage your preferences below.

    By clicking "Accept All", you consent to our use of cookies for analytics and personalized advertising. You can customize your preferences or reject non-essential cookies.

    Privacy PolicyTerms of Service