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:
- A virtual with the same name exists in a base
- Signature matches (parameters, cv/ref qualifiers, return type rules)
- 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:
- Name typos:
drawvsdrwa - Missing
const: basevoid f() constvs derivedvoid f() - Parameter type changes:
intvssize_t - Base API changes:
virtualremoved 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 class | final function | |
|---|---|---|
| Applies to | Whole class | One virtual function |
| Effect | No inheritance | No further override |
| Position | After class name | After virtual function |
Choosing between them
| Meaning | When | |
|---|---|---|
class D final | Cannot inherit D | You are sure the hierarchy ends here (security, invariants, perf). |
virtual void f() final | f cannot be overridden again | Lock 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/finalclass: may enable direct calls or inlining when the implementation can prove the final callee (no UB).overrideitself: 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.
Related posts (internal links)
- 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
Related posts
- async & launch
- Atomic operations
- Attributes
- auto keyword
- auto type deduction