C++ override & final: Virtual Overrides, Devirtualization, and API Sealing

C++ override & final: Virtual Overrides, Devirtualization, and API Sealing

이 글의 핵심

Use override to verify virtual overrides, final to close inheritance or virtual methods, and understand vtables, devirtualization, and profiling caveats.

What is override?

override (C++11) marks a member function as intended to override a base class virtual function. The compiler verifies a matching virtual in a base; typos and signature drift become errors instead of silent new overloads.

class Base {
public:
    virtual void func() {
        std::cout << "Base::func" << std::endl;
    }
};

class Derived : public Base {
public:
    void func() override {
        std::cout << "Derived::func" << std::endl;
    }
};

Why it matters:

  • Typos: wrong name → error with override
  • Signature checks: parameters, const, return (covariant rules)
  • Intent: readers see polymorphic intent immediately
  • Refactors: base changes surface errors in derived classes
class Base {
public:
    virtual void process(int x) {}
};

class Derived : public Base {
public:
    // Without override: typo silently creates a new function
    void proccess(int x) {}  // bug

    // With override: error—nothing to override
    void proccess(int x) override {}
};

Mechanics: override tells the compiler “this overrides a base virtual.” The compiler checks:

  1. A virtual with the same name exists in a base
  2. Signature matches (parameters, cv/ref qualifiers, return type rules)
  3. The base function is not final
class Base {
public:
    virtual void func(int x) const {}
};

class Derived : public Base {
public:
    // void func(int x) override {}           // error: missing const
    // void func(double x) const override {}  // error: wrong parameter
    void func(int x) const override {}        // OK
};

Virtual override in a nutshell

A virtual member in the base participates in dynamic dispatch. A derived class provides the same signature to override the vtable slot. Calls through base pointers/references invoke the derived implementation.

  • vtable: indirect calls through function pointer tables on typical implementations
  • Signature match: name, parameters, cv-qualifiers, ref qualifiers, and covariant returns must line up—otherwise you may accidentally add a new function
struct Base {
    virtual void f(int) const {}
    virtual ~Base() = default;
};
struct Derived : Base {
    void f(int) const override {}
};

Why override is important

Without override, these slip through quietly:

  1. Name typos: draw vs drwa
  2. Missing const: base void f() const vs derived void f()
  3. Parameter type changes: int vs size_t
  4. Base API changes: virtual removed or signature changed—derived code may silently stop overriding

With override, those cases are compile-time errors, protecting polymorphic contracts.

Benefits of override (example)

class Base {
public:
    virtual void func(int x) {}
};

class Derived : public Base {
public:
    void fucn(int x) {}           // typo: new function
    void fucn(int x) override {}  // error: nothing to override
};

What is final?

final (C++11) forbids further overriding (on a virtual function) or forbids inheriting the class (when applied to the class).

class FinalClass final {
public:
    void func() {}
};

// class Derived : public FinalClass {};  // error

class Base {
public:
    virtual void func() final {}
};

class Derived : public Base {
public:
    // void func() override {}  // error: final in base
};

Why use it:

  • Design intent: this type or function is not meant to grow further
  • Security: sensitive logic cannot be overridden
  • Optimization hints: may enable devirtualization
  • Safety: prevent unexpected subclasses
class SecurityManager final {
public:
    void authenticate() { /* ... */ }
};

Performance: if no further overrides exist, the compiler may devirtualize or inline in some contexts (implementation-dependent; profile hot paths).

final class vs final function:

final classfinal function
Applies toWhole classOne virtual function
EffectNo inheritanceNo further override
PositionAfter class nameAfter virtual function

Choosing between them

MeaningWhen
class D finalCannot inherit DYou are sure the hierarchy ends here (security, invariants, perf).
virtual void f() finalf cannot be overridden againLock one step of a pipeline while leaving other hooks open.

final on a class can help optimization by telling the compiler there are no more derived types. final on a function locks one method. Do not overuse if you may need extension later.

Practical examples

Example 1: Shapes

class Shape {
public:
    virtual double area() const = 0;
    virtual void draw() const = 0;
    virtual ~Shape() = default;
};

class Circle : public Shape {
    double radius;
public:
    Circle(double r) : radius(r) {}
    double area() const override {
        return 3.14159 * radius * radius;
    }
    void draw() const override {
        std::cout << "Drawing Circle" << std::endl;
    }
};

class Rectangle final : public Shape {
    double width, height;
public:
    Rectangle(double w, double h) : width(w), height(h) {}
    double area() const override final {
        return width * height;
    }
    void draw() const override {
        std::cout << "Drawing Rectangle" << std::endl;
    }
};

// class Square : public Rectangle {};  // error: Rectangle is final

Example 2: Loggers

class Logger {
public:
    virtual void log(const std::string& message) {
        std::cout << "[LOG] " << message << std::endl;
    }
    virtual ~Logger() = default;
};

class FileLogger : public Logger {
    std::ofstream file;
public:
    FileLogger(const std::string& filename) : file(filename) {}
    void log(const std::string& message) override {
        file << "[FILE] " << message << std::endl;
    }
};

class SecureLogger final : public FileLogger {
public:
    SecureLogger(const std::string& filename) : FileLogger(filename) {}
    void log(const std::string& message) override final {
        FileLogger::log("ENCRYPTED: " + message);
    }
};

Example 3: Template method

class GameCharacter {
public:
    void takeDamage(int damage) {
        beforeDamage();
        applyDamage(damage);
        afterDamage();
    }
    virtual ~GameCharacter() = default;
protected:
    virtual void beforeDamage() {}
    virtual void applyDamage(int damage) = 0;
    virtual void afterDamage() {}
};

class Warrior : public GameCharacter {
    int health = 100;
    int armor = 50;
protected:
    void beforeDamage() override {
        std::cout << "brace" << std::endl;
    }
    void applyDamage(int damage) override final {
        int actual = std::max(0, damage - armor);
        health -= actual;
        std::cout << "damage: " << actual << std::endl;
    }
    void afterDamage() override {
        if (health <= 0) {
            std::cout << "warrior down" << std::endl;
        }
    }
};

Example 4: Database interfaces

class IDatabase {
public:
    virtual void connect() = 0;
    virtual void disconnect() = 0;
    virtual void query(const std::string& sql) = 0;
    virtual ~IDatabase() = default;
};

class MySQLDatabase : public IDatabase {
public:
    void connect() override { std::cout << "MySQL connect" << std::endl; }
    void disconnect() override { std::cout << "MySQL disconnect" << std::endl; }
    void query(const std::string& sql) override {
        std::cout << "MySQL query: " << sql << std::endl;
    }
};

class PostgreSQLDatabase final : public IDatabase {
public:
    void connect() override { std::cout << "PostgreSQL connect" << std::endl; }
    void disconnect() override { std::cout << "PostgreSQL disconnect" << std::endl; }
    void query(const std::string& sql) override final {
        std::cout << "PostgreSQL query: " << sql << std::endl;
    }
};

override and const

class Base {
public:
    virtual void func() const {}
};

class Derived : public Base {
public:
    void func() override {}       // error: const mismatch
    void func() const override {} // OK
};

Common problems

Typos, signature drift, inheriting final classes, overriding final functions—override/final turn many of these into compile errors. Prefer composition over inheriting a final class when you need wrapping.

Combining override and final

class Base {
public:
    virtual void func1() {}
    virtual void func2() {}
};

class Middle : public Base {
public:
    void func1() override {}
    void func2() override final {}
};

class Leaf : public Middle {
public:
    void func1() override {}
    // void func2() override {}  // error: final in Middle
};

Performance: vtables, devirtualization, measurement

  • Typical virtual call: indirect branch via vtable—small cost, but can add up in hot loops.
  • final / final class: may enable direct calls or inlining when the implementation can prove the final callee (no UB).
  • override itself: zero runtime cost—purely compile-time checking; it prevents wrong virtual calls.

Practice: before adding final for speed, profile to see if virtual calls are the bottleneck. Closing APIs has bigger design trade-offs than nanoseconds.

Production patterns

Interface + final implementation

class IPaymentProcessor {
public:
    virtual void processPayment(double amount) = 0;
    virtual bool validateCard(const std::string& cardNumber) = 0;
    virtual ~IPaymentProcessor() = default;
};

class SecurePaymentProcessor final : public IPaymentProcessor {
public:
    void processPayment(double amount) override {
        if (validateCard(currentCard_)) {
            std::cout << "paid: $" << amount << '\n';
        }
    }
    bool validateCard(const std::string& cardNumber) override final {
        return cardNumber.length() == 16;
    }
private:
    std::string currentCard_;
};

Template method with final on the shell

class DataProcessor {
public:
    void process() final {
        loadData();
        validateData();
        transformData();
        saveData();
    }
    virtual ~DataProcessor() = default;
protected:
    virtual void loadData() = 0;
    virtual void validateData() = 0;
    virtual void transformData() = 0;
    virtual void saveData() = 0;
};

FAQ (selected)

Q: Always use override when overriding?
A: Yes—for every virtual override in new code.

Q: When to use final?
A: When extension is genuinely undesirable (security, invariants) or you have measured benefit—avoid locking APIs prematurely.

Q: Works without override?
A: Often yes at runtime—but override is strongly recommended.

Q: Performance upside of final?
A: Possible devirtualization/inlining—measure.

Q: override vs virtual in derived classes?
A: Base declares virtual; derived uses override (you do not write virtual override).

Q: override and final together?
A: Yes: void f() override final.

Q: Downsides of final classes?
A: You cannot subclass later—only use when sure.

Resources: Scott Meyers Effective Modern C++ Item 12, cppreference override, cppreference final.

One-line summary: override verifies virtual overrides; final stops inheritance or further overrides.


  • Inheritance & polymorphism
  • noexcept
  • std::ratio

Practical tips

Debugging

  • Read warnings; reproduce minimally

Performance

  • Profile before micro-optimizing

Code review

  • Follow conventions

Practical checklist

Before coding

  • Right technique?
  • Team can maintain?
  • Meets performance needs?

While coding

  • Warnings resolved?
  • Edge cases?
  • Error handling?

At review

  • Intent clear?
  • Tests?
  • Docs?

Keywords

C++, override, final, virtual function, devirtualization


  • async & launch
  • Atomic operations
  • Attributes
  • auto keyword
  • auto type deduction
... 996 lines not shown ... Token usage: 63706/1000000; 936294 remaining Start-Sleep -Seconds 3