Runtime polymorphism lets code call a method through a base-class pointer or reference and have the derived class's version actually run — the mechanism behind "one interface, many implementations."
class Shape {
public:
virtual double area() const { return 0; } // virtual enables overriding
virtual ~Shape() {} // virtual destructor — see below
};
class Circle : public Shape {
private:
double radius;
public:
Circle(double r) : radius(r) {}
double area() const override { // 'override' confirms this matches a virtual
return 3.14159 * radius * radius;
}
};
void printArea(const Shape &s) { // works for ANY Shape-derived type
std::cout << s.area() << "\n";
}
int main() {
Circle c(5);
printArea(c); // calls Circle's area(), not Shape's — resolved at RUNTIME
return 0;
}
Without the virtual keyword, calling .area() through a Shape& would always run Shape's version, no matter what derived type the object actually is — this is called "static binding," decided at compile time based purely on the reference/pointer's declared type. Marking area() virtual switches to dynamic binding: the actual call is resolved at runtime based on the object's real type, via an internal mechanism called a vtable (virtual function table) that each polymorphic class carries.
override (C++11) isn't strictly required, but should always be used when overriding a virtual function — it makes the compiler verify the function genuinely matches a base class's virtual signature, catching typos (a mismatched parameter type, a missing const) that would otherwise silently create an unrelated new function instead of an override, a notoriously hard bug to spot without this keyword.
A virtual destructor in the base class is essential whenever a derived object might be deleted through a base-class pointer (Shape *s = new Circle(5); delete s;) — without it, only Shape's destructor runs, Circle's never does, and any resources Circle owns leak. A pure virtual function (virtual double area() const = 0;) has no implementation in the base class at all, forcing every derived class to provide one and making the base class abstract — it can no longer be instantiated directly, only used as an interface.