Reuse and extend with inheritance, dynamic dispatch through virtual methods and the vtable, abstract interfaces, dynamic_cast/RTTI, and diamond inheritance solved with virtual bases.
A virtual function lets a base-class pointer call the derived override at runtime — this is dynamic (runtime) polymorphism. The compiler implements it with a vtable: a hidden per-class table of function pointers. Always give a polymorphic base a virtual destructor, or deleting through a base pointer leaks the derived part.
#include <iostream>
#include <memory>
#include <vector>
struct Shape {
virtual double area() const = 0; // pure virtual -> abstract class
virtual ~Shape() = default; // virtual dtor: essential
};
struct Circle : Shape {
double r;
Circle(double r) : r(r) {}
double area() const override { return 3.14159 * r * r; }
};
struct Square : Shape {
double s;
Square(double s) : s(s) {}
double area() const override { return s * s; }
};
int main() {
std::vector<std::unique_ptr<Shape>> shapes;
shapes.push_back(std::make_unique<Circle>(1.0));
shapes.push_back(std::make_unique<Square>(2.0));
for (const auto& s : shapes)
std::cout << s->area() << "\n"; // 3.14159 then 4 — the right override
return 0;
}A class with only pure virtual functions is an interface: it declares what implementers must do without any data or logic. Program to the interface (the base type) and you can swap implementations freely — the foundation of dependency inversion and testable design.
#include <iostream>
#include <string>
struct Logger { // interface
virtual void log(const std::string&) = 0;
virtual ~Logger() = default;
};
struct ConsoleLogger : Logger {
void log(const std::string& m) override { std::cout << "[log] " << m << "\n"; }
};
void run(Logger& out) { // depends on the interface only
out.log("started");
}
int main() {
ConsoleLogger c;
run(c); // inject any Logger
return 0;
}Runtime Type Information lets you ask, at runtime, whether a base pointer actually points at a specific derived type. dynamic_cast returns nullptr on a pointer that isn’t that type — a safe, checked down-cast. Reach for it sparingly; a virtual function is usually the cleaner answer.
#include <iostream>
struct Animal { virtual ~Animal() = default; };
struct Dog : Animal { void bark() const { std::cout << "woof\n"; } };
struct Cat : Animal {};
void speak(Animal* a) {
if (auto* d = dynamic_cast<Dog*>(a)) // is it really a Dog?
d->bark();
else
std::cout << "not a dog\n";
}
int main() {
Dog d; Cat c;
speak(&d); // woof
speak(&c); // not a dog
return 0;
}C++ allows inheriting from several bases. The classic pitfall is the diamond: if B and C both derive from A, and D derives from both, D gets two copies of A. virtual inheritance collapses them into one shared A, fixing ambiguity.
#include <iostream>
struct Device { int id = 0; };
struct Scanner : virtual Device {}; // virtual -> share one Device
struct Printer : virtual Device {};
struct Copier : Scanner, Printer {}; // one 'id', not two
int main() {
Copier c;
c.id = 7; // unambiguous thanks to virtual inheritance
std::cout << c.id << "\n"; // 7
return 0;
}