Virtual Functions & VTable
Part of the Object Oriented Programming section of Coddy's C++ journey — lesson 52 of 104.
The virtual keyword solves the problem we saw in the previous lesson. When you declare a method as virtual, C++ determines which version to call based on the actual object type at runtime, not the pointer type.
class Animal {
public:
virtual void speak() {
std::cout << "Some sound" << std::endl;
}
};
class Dog : public Animal {
public:
void speak() override {
std::cout << "Woof!" << std::endl;
}
};
Dog d;
Animal* ptr = &d;
ptr->speak(); // Output: Woof!Now the correct method is called! The override keyword is optional but recommended. It tells the compiler you intend to override a virtual function, catching errors if the signatures don't match.
How does this work? When a class has virtual functions, the compiler creates a virtual table (vtable). This is a hidden lookup table containing pointers to the class's virtual functions. Each object with virtual functions stores a hidden pointer (vptr) to its class's vtable.
When you call a virtual function through a pointer, the program looks up the correct function address in the vtable at runtime. This is called dynamic dispatch. It adds a small overhead compared to regular function calls, but enables powerful polymorphic behavior.
One important rule: if a class has virtual functions and will be used as a base class, its destructor should also be virtual. This ensures proper cleanup when deleting derived objects through base pointers:
class Animal {
public:
virtual ~Animal() {} // Virtual destructor
virtual void speak() {}
};Challenge
EasyLet's build a media player system that demonstrates the power of virtual functions and runtime polymorphism. You'll create a hierarchy of media types where each one plays differently, and see how virtual functions enable the correct behavior even when accessed through base class pointers.
You'll organize your code across three files:
Media.h: Define a baseMediaclass that represents any playable media:- A protected
std::string titlemember - A constructor that takes a title and stores it
- A virtual
play()method that prints:Playing media: <title> - A virtual destructor that prints:
Media [<title>] destroyed
- A protected
AudioTrack.h: Define anAudioTrackclass that publicly inherits fromMedia:- A private
std::string artistmember - A constructor that takes a title and artist, passes the title to the base class, and stores the artist
- Override
play()using theoverridekeyword to print:Playing audio: <title> by <artist> - A destructor that prints:
AudioTrack [<title>] destroyed
- A private
main.cpp: Read three inputs (each on a separate line):- Audio track title (string)
- Artist name (string)
- Video title (string)
Define a
VideoClipclass directly in main.cpp that publicly inherits fromMedia:- A private
int durationmember (in seconds) - A constructor that takes a title and duration (default duration to 120)
- Override
play()to print:Playing video: <title> (<duration>s) - A destructor that prints:
VideoClip [<title>] destroyed
Create an array of three
Media*pointers inside a block scope. Dynamically allocate:- A base
Mediaobject with title "Generic Media" - An
AudioTrackobject with the input title and artist - A
VideoClipobject with the input video title
Loop through the array and call
play()on each pointer. Then delete all objects in reverse order. After the block, print:Playback complete!
For example, with inputs Bohemian Rhapsody, Queen, and Nature Documentary:
Playing media: Generic Media
Playing audio: Bohemian Rhapsody by Queen
Playing video: Nature Documentary (120s)
VideoClip [Nature Documentary] destroyed
Media [Nature Documentary] destroyed
AudioTrack [Bohemian Rhapsody] destroyed
Media [Bohemian Rhapsody] destroyed
Media [Generic Media] destroyed
Playback complete!Notice how calling play() through Media* pointers invokes the correct derived class method thanks to virtual functions. Also observe how the virtual destructor ensures proper cleanup — both the derived and base destructors run when deleting through a base pointer.
Cheat sheet
The virtual keyword enables runtime polymorphism by determining which method to call based on the actual object type, not the pointer type:
class Animal {
public:
virtual void speak() {
std::cout << "Some sound" << std::endl;
}
};
class Dog : public Animal {
public:
void speak() override {
std::cout << "Woof!" << std::endl;
}
};
Dog d;
Animal* ptr = &d;
ptr->speak(); // Output: Woof!The override keyword is optional but recommended. It tells the compiler you intend to override a virtual function, catching errors if the signatures don't match.
How virtual functions work: The compiler creates a virtual table (vtable) containing pointers to the class's virtual functions. Each object with virtual functions stores a hidden pointer (vptr) to its class's vtable. When calling a virtual function through a pointer, the program looks up the correct function address in the vtable at runtime. This is called dynamic dispatch.
Virtual destructors: If a class has virtual functions and will be used as a base class, its destructor should also be virtual. This ensures proper cleanup when deleting derived objects through base pointers:
class Animal {
public:
virtual ~Animal() {} // Virtual destructor
virtual void speak() {}
};Try it yourself
#include <iostream>
#include <string>
#include "Media.h"
#include "AudioTrack.h"
using namespace std;
// TODO: Define VideoClip class here that publicly inherits from Media
// - Private int duration member (in seconds)
// - Constructor takes title and duration (default duration to 120)
// - Override play() to print: Playing video: <title> (<duration>s)
// - Destructor prints: VideoClip [<title>] destroyed
class VideoClip : public Media {
private:
int duration;
public:
// TODO: Implement constructor
VideoClip(const std::string& t, int d = 120) : Media(t) {
// TODO: Store the duration
}
// TODO: Override play() method
void play() override {
// TODO: Implement this method
}
// TODO: Implement destructor
~VideoClip() {
// TODO: Implement this destructor
}
};
int main() {
// Read inputs
string audioTitle;
string artist;
string videoTitle;
getline(cin, audioTitle);
getline(cin, artist);
getline(cin, videoTitle);
// TODO: Create a block scope with curly braces
// Inside the block:
// 1. Create an array of three Media* pointers
// 2. Dynamically allocate:
// - A base Media object with title "Generic Media"
// - An AudioTrack object with the input title and artist
// - A VideoClip object with the input video title
// 3. Loop through the array and call play() on each pointer
// 4. Delete all objects in reverse order
// TODO: After the block, print: Playback complete!
return 0;
}
This lesson includes a short quiz. Start the lesson to answer it and track your progress.
All lessons in Object Oriented Programming
1Fundamentals of OOP
External FilesC++ Build & CompilationHeader Files & Source FilesNamespaces & ScopeIntroduction to OOP in C++Classes vs ObjectsThe 'this' PointerMethods (Member Functions)Attributes (Data Members)Ctors & Dtors BasicsRecap - Simple Calculator4Class Properties
Instance vs Static MembersGetters and SettersConst Member FunctionsMutable KeywordStatic Methods and VariablesFriend Functions & ClassesRecap - Bank Account Manager7Inheritance
Basic InheritanceInheritance Access LevelsCtor & Dtor Call OrderMethod OverridingVirtual Functions & VTableMultiple InheritanceVirtual InheritanceRecap - Employee Hierarchy2Memory Management
Stack vs Heap MemoryPointers and ReferencesDynamic Memory (new/delete)Smart Pointers in C++RAII in C++Recap - Dynamic Array Manager5Encapsulation
Access Specifiers in C++Access Specifiers In DepthInformation HidingStruct vs ClassNested & Inner ClassesRecap - Student Records System8Polymorphism
Compile vs Runtime PolymorphFunction OverloadingVirtual Functions RevisitedPure Virtual FunctionsAbstract ClassesInterface Design in C++Dynamic Casting & RTTIRecap - Shape Calculator3Constructors & Destructors
Default ConstructorParameterized ConstructorCopy ConstructorMove ConstructorConstructor Init ListsDelegating ConstructorsDestructor Deep DiveRule of Three / Five / ZeroRecap - String Class6Operator Overloading
Intro to Operator OverloadArithmetic Operator OverloadComparison Operator OverloadStream OperatorsAssignment Operator Overload[] and () Operator OverloadType Conversion OperatorsRecap - Matrix Class9Templates
Function TemplatesClass TemplatesTemplate SpecializationVariadic TemplatesSFINAE & Type Traits BasicsRecap - Generic Container