thecodex.expert · The Codex Family of Knowledge
C++

Inheritance and Polymorphism

Forget the virtual keyword, and calling an overridden method through a base-class pointer silently calls the BASE version instead — a real, genuinely common C++ bug that simply doesn’t exist in Java.

C++20 / C++23 Not virtual by default Last verified:
Canonical Definition

A derived class inherits its base class's members via class Derived : public Base. Crucially, a method only participates in runtime polymorphism (dynamic dispatch through a base pointer/reference) if it's marked virtual in the base class — without that keyword, C++ resolves the call at compile time based on the pointer's declared type, not the object's actual type, the opposite of Java's always-virtual default. The override keyword (C++11+) lets the compiler verify a method genuinely overrides a virtual base method, catching a signature mismatch that would otherwise silently create an unrelated new method instead.

Without virtual: polymorphism silently doesn't happen

Even though pet actually points to a Dog, calling makeSound() through the Animal-typed pointer calls Animal's version — the compiler resolved this at COMPILE time based on the pointer's declared type, since nothing here is marked virtual.

C++no_virtual.cpp
class Animal {
public:
    void makeSound() { std::cout << "...\n"; }   // NOT virtual
};

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

Animal* pet = new Dog();
pet->makeSound();   // prints "..." — the BASE version, NOT "Woof" — a real, common bug

With virtual: true runtime polymorphism

The single-word virtual change flips the behavior entirely — now the ACTUAL object's type decides which version runs, exactly like Java's default behavior.

C++with_virtual.cpp
class Animal {
public:
    virtual void makeSound() { std::cout << "...\n"; }   // virtual — enables polymorphism
    virtual ~Animal() = default;   // ALSO needs a virtual destructor — see below
};

class Dog : public Animal {
public:
    void makeSound() override {   // override: compiler VERIFIES this actually overrides
        std::cout << "Woof\n";
    }
};

Animal* pet = new Dog();
pet->makeSound();   // prints "Woof" — now genuinely polymorphic
delete pet;

Virtual destructors: required, not optional, for polymorphic base classes

Without a virtual destructor, deleting a Dog through an Animal* only runs Animal's destructor, never Dog's — any resources Dog itself owns leak, and this is genuine undefined behavior, not just a style preference.

C++virtual_destructor.cpp
class Base {
public:
    virtual ~Base() = default;   // REQUIRED if any method is virtual and deletion happens via Base*
};

class Derived : public Base {
    std::vector<int> data;   // owns a resource
public:
    ~Derived() { /* cleans up data */ }
};

Base* obj = new Derived();
delete obj;   // correctly calls Derived's destructor first, THEN Base's, because ~Base() is virtual

Sources

1
ISO/IEC 14882 (C++ Standard), "Virtual functions" and "Virtual destructors," cppreference.com/w/cpp/language/virtual.