Skip to content
C++
Language
since C++11
Basic

override and final

Contextual keywords override and final (C++11) let the compiler verify virtual function overrides and block further overriding or derivation.

override / finalsince C++11

override and final are contextual specifiers that make the compiler verify that a member function overrides a base-class virtual function (override), and forbid further overriding of a virtual function or further derivation from a class (final).

Overview

Before C++11, overriding a virtual function was implicit: any derived-class member with the same name, parameter types, cv-qualifiers and ref-qualifiers as a base virtual function silently became an override. A typo in the signature, a missing const, or a later change to the base class produced a new virtual function (or a hiding non-virtual one) with no diagnostic. The program compiled, and calls through base pointers dispatched to the base implementation.

C++11 added two specifiers to close this gap:

  • override states intent. If the function does not override a virtual function in some base class, the program is ill-formed.
  • final states a closure. On a virtual function, no derived class may override it. On a class, no class may derive from it.

Both are contextual keywords: they have special meaning only in the positions described below, so existing code that uses override or final as identifiers still compiles.

Syntax

cpp
struct Base {
    virtual void f(int);
    virtual void g() const;
    virtual ~Base() = default;
};

struct D : Base {
    void f(int) override;          // C++11: checked against Base::f(int)
    void g() const override final; // C++11: checked, and closed to further overriding
};

struct Leaf final : D {};          // C++11: no class may derive from Leaf

Placement rules:

  • In a member function declaration, both specifiers come after the declarator, after cv/ref-qualifiers and noexcept, and before a trailing return type is not allowed β€” they follow it: auto f() const -> int override;.
  • For pure-specifier combinations: void f() override = 0; and void f() override = default; (for special members such as a destructor) are valid in that order.
  • For classes, final appears after the class name: class X final : public Base { };. A final class may not be a base, though it may itself derive from others.
  • override and final may be combined in either order. final on a function that is not virtual and does not override anything is an error; final implies the function is virtual only if it overrides one.
  • The specifiers appear only in the declaration inside the class. An out-of-class definition omits them.
  • virtual is optional on an overrider, since the function is implicitly virtual. With override present, repeating virtual is redundant.

Examples

Catching signature drift

cpp
struct Shape {
    virtual double area() const = 0;
    virtual void scale(double factor);
    virtual ~Shape() = default;
};

struct Circle : Shape {
    explicit Circle(double r) : r_(r) {}
    double area() const override { return 3.14159265 * r_ * r_; }

    // void scale(float) override;   // error: does not override (parameter is float)
    // double area() override;       // error: missing const -> different signature
    void scale(double factor) override { r_ *= factor; }
private:
    double r_;
};

Without override, both commented declarations would compile as unrelated members and Shape::scale would be called through a Shape&. Many compilers also provide a warning for a missed override (-Wsuggest-override in GCC/Clang, -Winconsistent-missing-override in Clang) when some, but not all, overriders use the specifier. Enabling these makes the habit enforceable.

Refactoring safety

cpp
struct Handler {
    virtual void on_event(int id);            // later changed to: on_event(int id, bool sync)
    virtual ~Handler() = default;
};

struct Logger : Handler {
    void on_event(int id) override;           // now a hard error after the base changes
};

When the base signature changes, every stale overrider fails to compile, which is exactly what you want in a large hierarchy.

Sealing a function and a class

cpp
class Connection {
public:
    virtual void open();
    virtual void close();
    virtual ~Connection() = default;
};

class TlsConnection : public Connection {
public:
    void open() override;
    void close() override final;   // C++11: subclasses may customize open() but not close()
};

class PinnedTlsConnection final : public TlsConnection {
public:
    void open() override;          // OK
    // void close() override;      // error: Connection::close is final in TlsConnection
};

// class Foo : public PinnedTlsConnection {};   // error: base is final

Devirtualization

cpp
struct Codec {
    virtual int decode(const char*, int) = 0;
    virtual ~Codec() = default;
};

struct Utf8Codec final : Codec {
    int decode(const char* p, int n) override;
};

int run(Utf8Codec& c, const char* p, int n) {
    return c.decode(p, n);   // compiler may call Utf8Codec::decode directly, even inline
}

Because Utf8Codec is final, the dynamic type of a Utf8Codec& is known exactly, so the compiler can replace the vtable dispatch with a direct call. The same applies to a final function called through a pointer or reference to a class that declares it. This is a permitted optimization, not a guarantee; modern compilers can often devirtualize without final using whole-program or LTO information, but final makes it reliable.

Destructors

cpp
struct Base { virtual ~Base() = default; };
struct Derived : Base {
    ~Derived() override = default;   // C++11: legal; checks that Base has a virtual destructor
};

Using override on a destructor is valid and turns a missing virtual in the base into a compile error. It is a useful check, though some style guides prefer to omit it on destructors.

Best Practices

  • Use override on every overrider. Treat a missing specifier as a defect. Enable -Wsuggest-override (GCC) or -Winconsistent-missing-override (Clang, on by default), or MSVC's C5204/C4263/C4264 family as appropriate.
  • Do not combine virtual with override. The core guidelines recommend exactly one of virtual, override, or final on a function declaration, with override/final for derived classes. override final together is acceptable when you want to state both.
  • Prefer final on leaf classes that are not designed for inheritance. It documents intent and enables devirtualization.
  • Be careful sealing library types. A final class cannot be mocked by subclassing or extended by clients. Seal in application code freely; in public APIs, do so deliberately.
  • Seal individual functions sparingly when a template-method pattern requires an invariant step (e.g. close() that always releases a resource).
  • Mark final non-virtual helpers' classes, not functions. final on a function only makes sense for a virtual one.

Common Pitfalls

  • Assuming override fixes hiding. It only checks the declaration it is attached to. A same-named overload with a different signature that lacks override still hides base overloads; use using Base::f; to bring them back into scope.
  • Const and ref-qualifier mismatches. void f() const and void f() are different signatures, as are void f() & and void f() &&. These are the most common silent non-overrides in pre-C++11 code.
  • Covariant return types are still allowed. An overrider may return a pointer or reference to a more derived class, and override accepts it. Changing to an unrelated return type is an error.
  • Different default arguments. Default arguments are bound statically by the declared type of the expression. An overrider with different defaults compiles with override, yet calls through a base reference use the base defaults. Avoid defaults on virtual functions.
  • Wrong noexcept. An overrider may not have a looser exception specification than the base function. Since C++17, noexcept is part of the function type, but the override rule still requires the overrider to be at least as restrictive.
  • Ordering of specifiers. void f() override const; is ill-formed; cv- and ref-qualifiers precede override/final, and noexcept precedes them too.
  • Private overriders. Access control is checked on the static type, not the dynamic one, so a private overrider still dispatches through a public base declaration. override does not change this.
  • final is not const for design. Marking a class final after clients have already derived from it is a breaking change.
  • Using the names elsewhere. Because they are contextual, int override = 0; is legal. It is poor style and confuses readers and tooling; avoid it.

See Also

  • Abstract classes: pure virtual functions, which override can implement and final can seal.