본문으로 건너뛰기
Previous
Next
C++ override & final: Virtual Overrides, Devirtualization,

C++ override & final: Virtual Overrides, Devirtualization,

C++ override & final: Virtual Overrides, Devirtualization,

이 글의 핵심

C++ override and final: catching signature mistakes, sealing classes and virtual functions, devirtualization and performance notes, and practical patterns with interfaces.

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.

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


같이 보면 좋은 글 (내부 링크)

이 주제와 연결되는 다른 글입니다.


이 글에서 다루는 키워드 (관련 검색어)

C++, override, final, Virtual Function, C++11 등으로 검색하시면 이 글이 도움이 됩니다.