Inheritance and Virtual Functions
Deriving one class from another, and why the virtual keyword is what actually enables polymorphism.
What you'll learn
- Derive a class from a base class with public inheritance
- Explain why calling a non-virtual method through a base reference uses static (compile-time) binding
- Predict how adding the virtual keyword changes a method call to dynamic (runtime) dispatch
Explanation
Inheritance lets one class derive from another, reusing and extending its interface: class Dog : public Animal { ... } makes Dog a kind of Animal, inheriting whatever Animal declares. This lets code written to work with an Animal reference or pointer also work with any specific kind of animal derived from it, like Dog or Cat, without knowing about each concrete type in advance.
But inheritance alone doesn't give you polymorphism -- calling the derived type's version of a method through a base reference or pointer. By default, C++ member function calls use static binding: which version of speak() runs is decided at compile time, based on the declared (static) type of the reference or pointer you're calling through, not the actual (dynamic) type of the object it refers to. If Animal::speak isn't marked virtual, calling animal.speak() through an Animal& always calls Animal's version, even if the real object underneath is a Dog with its own speak().
Marking the base class's method virtual (and the derived class's version with override, which asks the compiler to verify it's genuinely overriding something) changes this to dynamic dispatch: the actual call is resolved at runtime, based on the object's real, dynamic type -- so calling speak() through an Animal& that actually refers to a Dog now correctly calls Dog::speak(). This is the mechanism that makes polymorphism -- "write code against the base type, get the derived type's behavior automatically" -- actually work in C++; without virtual, you don't get it, even though the inheritance relationship itself is exactly the same either way.
Guided lab
Guided edit: From static binding to virtual dispatch
Follow each step to see exactly how adding the virtual keyword changes which speak() this program actually calls.
Step 1 of 2
Start with a non-virtual speak() method. makeSpeak takes a const Animal& parameter -- even though the actual object passed in is a Dog, the call to animal.speak() is resolved at compile time based on the parameter's declared type, Animal.
#include <iostream>
class Animal {
public:
void speak() const {
std::cout << "Some generic animal sound" << std::endl;
}
};
class Dog : public Animal {
public:
void speak() const {
std::cout << "Woof!" << std::endl;
}
};
void makeSpeak(const Animal &animal) {
animal.speak();
}
int main() {
Dog dog;
makeSpeak(dog);
return 0;
}Stuck? Get a hint.
Common mistakes
- Assuming inheritance alone gives you polymorphism -- without the virtual keyword, a base-type call always uses the base class's version, regardless of the object's real type.
- Forgetting to mark a base class method virtual when derived classes are meant to override its behavior for calls made through a base reference or pointer.
- Omitting a virtual destructor on a base class that's meant to be deleted polymorphically through a base pointer, which is a separate but related pitfall from method dispatch.
Knowledge check
Takeaway
Inheritance alone gives you static binding to the base class's version; marking a method virtual is what switches a base-reference call to the derived object's actual, dynamic-dispatched version.
Summary
A derived class inherits from a base class with `: public Base`; without `virtual`, base-reference calls resolve at compile time to the base version, while `virtual` (with `override` on the derived side) enables true runtime polymorphism.
References
Your notes
Notes save automatically.
Finished this lesson?
Mark it complete to track your progress and schedule a future review.
AI tutor
The optional AI tutor isn't enabled in this deployment. All lessons, exercises, quizzes, and search work fully without it.