A virtual function is dispatched on the object's dynamic type at run time — the mechanism behind runtime polymorphism.
Static vs dynamic binding
#include <iostream>
#include <memory>
#include <vector>
struct Shape {
virtual double area() const { return 0; }
std::string kind() const { return "shape"; } // non-virtual
virtual ~Shape() = default; // virtual destructor!
};
struct Rect : Shape {
double w, h;
Rect(double a, double b) : w(a), h(b) {}
double area() const override { return w * h; } // `override`: compiler checks it overrides
std::string kind() const { return "rect"; } // hides, doesn't override
};
struct Circle : Shape {
double r;
explicit Circle(double x) : r(x) {}
double area() const override { return 3.14 * r * r; }
};
int main() {
std::vector<std::unique_ptr<Shape>> shapes;
shapes.push_back(std::make_unique<Rect>(2, 3));
shapes.push_back(std::make_unique<Circle>(1));
for (const auto& s : shapes) std::cout << s->kind() << ' ' << s->area() << '\n';
Rect r(1, 1);
Shape& ref = r;
std::cout << ref.area() << ' ' << ref.kind() << ' ' << r.kind() << '\n';
}shape 6
shape 3.14
1 shape rect| Call through… | Non-virtual function | Virtual function |
|---|---|---|
Object r.f() | Static type | Static type (= dynamic) |
Pointer/reference to base p->f() | Static type (Shape::f) | Dynamic type (Rect::f) |
How it works: vptr & vtable
vtable per class
Each class with virtual functions gets one static table of function pointers, one slot per virtual function.
vptr per object
Every object carries a hidden pointer (usually the first 8 bytes) to its class's vtable. Constructors set it.
Call
p->area()compiles to: loadp->vptr, load slot k, call it — one or two extra memory loads.
#include <iostream>
struct Plain { int x; void f() {} };
struct Virtual { int x; virtual void f() {} virtual ~Virtual() = default; };
int main() {
std::cout << sizeof(Plain) << ' ' << sizeof(Virtual) << '\n'; // 4 vs 16: vptr (8) + int + padding
}4 16override and final
struct Base {
virtual void save(int version) const {}
virtual ~Base() = default;
};
struct Derived : Base {
void save(long version) const override {} // error: doesn't override (parameter type differs)
};
int main() {}#include <iostream>
struct Base { virtual void run() const { std::cout << "base\n"; } virtual ~Base() = default; };
struct Leaf final : Base { // no class may derive from Leaf
void run() const override { std::cout << "leaf\n"; }
};
struct Mid : Base { void run() const final { std::cout << "mid\n"; } }; // run can't be overridden further
int main() {
const Base& b = Leaf{};
b.run();
Mid{}.run();
}leaf
midVirtual destructors
#include <iostream>
struct Base {
virtual void speak() const {}
~Base() { std::cout << "~Base\n"; } // NOT virtual
};
struct Derived : Base {
int* data = new int[100];
~Derived() { delete[] data; std::cout << "~Derived\n"; }
};
int main() {
Base* p = new Derived;
delete p; // undefined behaviour: only ~Base would run → ~Derived skipped, leak
}error: delete called on non-final 'Base' that has virtual functions but non-virtual destructor [-Werror,-Wdelete-non-abstract-non-virtual-dtor]Object slicing
#include <iostream>
struct Animal { virtual const char* sound() const { return "..."; } virtual ~Animal() = default; };
struct Cat : Animal { const char* sound() const override { return "meow"; } };
void byValue(Animal a) { std::cout << a.sound() << '\n'; } // copies only the Animal part
void byRef(const Animal& a) { std::cout << a.sound() << '\n'; }
int main() {
Cat c;
byValue(c);
byRef(c);
}...
meowCalling virtuals in constructors
#include <iostream>
struct Base {
Base() { hello(); } // dispatches to Base::hello, not Derived's!
virtual void hello() const { std::cout << "Base::hello\n"; }
virtual ~Base() = default;
};
struct Derived : Base { void hello() const override { std::cout << "Derived::hello\n"; } };
int main() { Derived d; d.hello(); }Base::hello
Derived::helloHow are virtual functions implemented?
Why is calling a virtual function from a constructor dangerous?