🌎 δΈ­ζ–‡ | English

final and override - Explicit Control of Virtual Function Behavior

final and override are two context-sensitive identifiers introduced in C++11, used in virtual-function inheritance to explicitly express the intent of overriding and sealing, allowing the compiler to surface polymorphism mismatches at compile time that would otherwise only show up as runtime bugs.

Why were they introduced?

  • Before C++11, whether a derived class actually overrode a base virtual function relied entirely on programmers checking signatures by hand β€” a single mismatched parameter would silently turn an override into a name-hiding declaration with no compiler warning
  • There was no standard way to express the design intent that "this type, or this polymorphic chain, ends here"
  • They make the design contract of virtual functions readable and verifiable

What's the difference between the two?

  • override: applied after a derived-class member function, explicitly declaring "this function overrides a base-class virtual function", so the compiler can verify it
  • final: applied after a virtual function means "this virtual function cannot be further overridden"; applied after a class means "this class cannot be inherited from"

I. Basic Usage and Scenarios

override - Explicitly Declare an Override

Without override, even a typo in the derived signature only becomes "a brand-new ordinary function" β€” the polymorphic behavior is silently lost.

struct Base {
    virtual void func(int) { }
};

struct Derived : Base {
    void func(double) { } // intended to override, but the parameter type is wrong;
                          // this actually declares a new function
};

With override, the same mistake is rejected at compile time.

struct Derived : Base {
    void func(double) override; // error: no matching virtual function in any base class
};

Only a base virtual function whose signature (return type + parameter list + cv-qualifiers + ref-qualifiers) matches exactly will satisfy override.

struct Base {
    virtual void func(int);
};

struct Derived : Base {
    void func(int) override; // ok
};

final - Forbid Further Overriding or Inheritance

final has two usages targeting different things.

On a virtual function - cut off the polymorphic chain

struct A {
    virtual void func() final { }
};

struct B : A {
    void func() override; // error: A::func is final and cannot be overridden
};

On a class - forbid inheritance

struct B final { };

struct C : B { }; // error: B is final and cannot be inherited from

final + Pure Virtual - Non-Overridable Template Method (NVI)

Lock the outer interface with virtual ... final, and expose the customizable steps as pure virtual functions. The result is a stable interface where the execution order cannot be changed but each step is customizable. This is a concise expression of the Non-Virtual Interface idiom.

struct AudioPlayer {
    virtual void play() final {  // subclasses cannot change the overall flow of play
        init_audio_params();
        play_audio();
    }
private:
    virtual void init_audio_params() = 0; // left for subclasses to customize
    virtual void play_audio() = 0;
};

struct WAVPlayer : AudioPlayer {
    void init_audio_params() override { /* ... */ }
    void play_audio() override { /* ... */ }
};

struct MP3Player : AudioPlayer {
    void init_audio_params() override { /* ... */ }
    void play_audio() override { /* ... */ }
};

Callers always use the unified AudioPlayer::play(); each format's player only needs to implement the two hooks. This structure is common when designing plugin-style or protocol-style interfaces.

Context-Sensitive Identifiers

Neither override nor final is a reserved word or a keyword β€” they are context-sensitive identifiers. They only carry these meanings when they appear at specific positions in a virtual function declaration or a class declaration; in any other position they can still be used as variable names, type names, namespace names, etc.

B override; // ok: here override is just an ordinary variable name
B final;    // ok: here final is just an ordinary variable name

This is a deliberate compromise in the C++ standard for backward compatibility: existing code that uses override or final as identifiers won't fail to compile after upgrading to C++11.

II. Important Notes

override Requires a Signature-Matching Base Virtual Function

Once override is added to a derived-class member function, the compiler requires a virtual function with a matching signature to exist in some base class β€” otherwise it's a compile error. This is the core value of override: lifting "override mismatch" silent bugs from runtime to compile time.

struct A {
    virtual void func1() { }
    void func2() { } // note: not virtual
};

struct B : A {
    void func1() override; // ok
    void func2() override; // error: A::func2 is not virtual
};

A final Class Is "Sealed" - Use With Care

A final class cannot be inherited from at all, not even to add a couple of helper methods. Marking a class final is essentially committing to "this type is, by design, a leaf node". Some rules of thumb:

  • The type is explicitly not meant to be extended further (e.g. error types, framework-internal implementation classes, singletons) -> a good fit for final
  • A general-purpose base class or a framework-provided extension point -> do not casually add final

final Only Applies to Virtual Functions

An ordinary member function cannot be overridden in the first place, so adding final to it is meaningless and the compiler will reject it.

struct A {
    void func() final; // error: final cannot be applied to a non-virtual function
};

override and final Can Be Used Together

If a virtual function should both override the base-class version and forbid further overriding in derived classes, you can combine the two.

struct B : A {
    void func() override final; // overrides A::func, and prevents C from overriding it again
};

III. Practice Code

Practice Topics

Practice Code Auto-detection Command

d2x checker final-and-override

IV. Additional Resources