OSC++.018: Virtual Functions and Polymorphism Basics

OSC++.018 cover showing a VS Code Dark+ C++ example where an Animal reference calls an overridden Dog speak function

Polymorphism means the same kind of call can produce different behavior depending on the actual object.

In C++, a virtual function lets a derived class provide its own version of a base-class function and have that version selected through a base-class reference or pointer.

Start with one simple idea:

Animal says: "Animal"
Dog says:    "Woof"
Cat says:    "Meow"

The smallest useful example

#include <iostream>

class Animal {
public:
    virtual void speak() {
        std::cout << "Animal\n";
    }
};

class Dog : public Animal {
public:
    void speak() override {
        std::cout << "Woof\n";
    }
};

int main() {
    Dog dog;
    Animal& pet = dog;

    pet.speak();
}

Output:

Woof

The variable pet is an Animal reference, but it refers to a real Dog object. Because speak() is virtual, C++ calls Dog::speak().

Video 1: Virtual functions and override

This focused C++ lesson demonstrates virtual functions and overriding in an inheritance hierarchy.

What does virtual do?

Look at the base class:

virtual void speak()

The keyword virtual tells C++ that derived classes may provide their own version and that the program should select the appropriate overridden function when calling through a base reference or pointer.

Without getting into the internal machinery yet, think:

virtual = let the actual object choose the overridden behavior.

What does override do?

void speak() override

override tells the compiler, “I intend this function to override a virtual function from a base class.”

That is useful because the compiler can catch mistakes. If the function does not actually match a virtual base-class function, the compiler reports an error instead of silently accepting your intention.

Video 2: Runtime method selection

This beginner lesson connects virtual functions to runtime polymorphism and method selection.

Add a Cat

class Cat : public Animal {
public:
    void speak() override {
        std::cout << "Meow\n";
    }
};

Now both Dog and Cat are kinds of Animal, but each can respond differently to the same speak() call.

Dog dog;
Cat cat;

Animal& first = dog;
Animal& second = cat;

first.speak();
second.speak();

Output:

Woof
Meow

Same base-class interface. Different actual objects. Different behavior. That is the beginner version of runtime polymorphism.

How this connects to inheritance

The previous lesson, OSC++.017: Inheritance Basics, introduced base and derived classes.

Inheritance gives us the relationship:

Animal
  ├── Dog
  └── Cat

Virtual functions add behavior to that relationship:

Animal reference → Dog object → Dog behavior
Animal reference → Cat object → Cat behavior

Simple game example

Imagine a game with different characters. Every character can perform an action, but the action is different for each character.

class Character {
public:
    virtual void action() {
        std::cout << "Character acts\n";
    }
};

class Miner : public Character {
public:
    void action() override {
        std::cout << "Miner deploys an ASIC\n";
    }
};

class Engineer : public Character {
public:
    void action() override {
        std::cout << "Engineer repairs equipment\n";
    }
};

The game can work with a Character interface while each real character supplies its own action.

Video 3: Polymorphism, virtual functions, and abstract classes

This beginner OOP tutorial reinforces polymorphism and virtual functions and previews how the idea extends into abstract classes.

Reference vs. copy

For this lesson, notice the ampersand:

Animal& pet = dog;

This makes pet a reference to the existing dog object. We are not creating a separate Animal copy.

This connects back to OSC++.006: References and Pass by Reference.

Common beginner mistakes

  • Forgetting virtual on the base-class function when runtime overriding is intended.
  • Leaving off override and making a signature mistake that the compiler could have caught.
  • Thinking polymorphism means every function must be virtual.
  • Confusing overriding with overloading. Overriding replaces inherited virtual behavior in a derived class; overloading uses the same function name with different parameter lists.
  • Trying to learn vtables before understanding the simple behavior first.

Quick practice

  1. Create a base class named Vehicle.
  2. Add a virtual function named move().
  3. Create Car and Bike classes that inherit from Vehicle.
  4. Override move() in both classes.
  5. Create a Car and a Bike.
  6. Use Vehicle& references to call move() and observe the different output.

Key takeaway

virtual enables runtime selection of overridden behavior through a base-class interface, and override lets the compiler verify that a derived function really overrides a base virtual function. In plain English: the program can say “speak” to an Animal and still get the correct Dog or Cat behavior.

Leave a comment