SyntaxStudy
Sign Up
C++ Virtual Functions and the vtable
C++ Beginner 1 min read

Virtual Functions and the vtable

Runtime polymorphism in C++ is achieved through virtual functions. When a member function is declared 'virtual' in a base class, the compiler generates a virtual table (vtable) — a per-class array of function pointers. Each polymorphic object stores a hidden pointer (vptr) to its class's vtable. A virtual call dereferences this pointer at runtime to invoke the correct overriding function, enabling the right behaviour regardless of the static type of the pointer or reference used. The 'override' specifier, introduced in C++11, asks the compiler to verify that a function actually overrides a virtual function in a base class. Without it, a typo in the function signature silently creates a new non-virtual function instead of overriding the base. Using 'override' consistently is a best practice that catches such errors at compile time. The 'final' specifier can be applied to a class or to an individual virtual function. A 'final' class cannot be inherited from, and a 'final' function cannot be overridden further. This gives the compiler freedom to devirtualise calls and can improve performance in tight loops, in addition to communicating design intent.
Example
#include <iostream>
#include <memory>
#include <vector>
#include <cmath>

class Shape {
public:
    virtual ~Shape() = default;

    // Pure virtual — every concrete shape must implement this
    virtual double area()        const = 0;
    virtual double perimeter()   const = 0;
    virtual std::string describe() const = 0;

    // Non-virtual template method uses virtual helpers
    void report() const {
        std::cout << describe()
                  << "  area="      << area()
                  << "  perimeter=" << perimeter() << "\n";
    }
};

class Circle final : public Shape {
public:
    explicit Circle(double r) : r_(r) {}
    double area()      const override { return M_PI * r_ * r_; }
    double perimeter() const override { return 2 * M_PI * r_; }
    std::string describe() const override { return "Circle(r=" + std::to_string(r_) + ")"; }
private:
    double r_;
};

class Rectangle : public Shape {
public:
    Rectangle(double w, double h) : w_(w), h_(h) {}
    double area()      const override { return w_ * h_; }
    double perimeter() const override { return 2 * (w_ + h_); }
    std::string describe() const override {
        return "Rect(" + std::to_string(w_) + "x" + std::to_string(h_) + ")";
    }
private:
    double w_, h_;
};

int main() {
    std::vector<std::unique_ptr<Shape>> shapes;
    shapes.push_back(std::make_unique<Circle>(5.0));
    shapes.push_back(std::make_unique<Rectangle>(4.0, 6.0));

    for (const auto& s : shapes)
        s->report();   // virtual dispatch selects correct overrides

    return 0;
}