The Definitive Guide to the C++ final Keyword

The C++ final keyword is a powerful tool that prevents class inheritance and virtual function overriding. While primarily used to enforce design constraints, final can also influence compiler optimizations and program performance.

What is final in C++?

The C++ final specifier was introduced in C++11 to prevent further inheritance of a class or overriding of a virtual function. It serves two primary purposes:

  1. Preventing Class Inheritance: Marking a class as final stops it from being used as a base class.
  2. Preventing Virtual Function Overriding: When applied to a virtual function, it ensures that no derived class can override that function.

This feature helps improve program safety, maintainability, and potential optimizations. In some cases, allows the compiler to generate more optimized code.

Why Was final Introduced?

Before final, C++ had no built-in mechanism to completely prevent class inheritance or virtual function overriding at the language level. Developers had to rely on private constructors or deleted destructors to restrict inheritance, which was not intuitive and often required extra workarounds. With the introduction of final, C++ now provides a clear and explicit way to enforce such constraints.

C++ final: Syntax and Usage

The C++ final keyword can be used in two contexts:

Preventing Class Inheritance

class Base final {  // 'final' prevents further inheritance
public:
    void show() {
        std::cout << "Base class\n";
    }
};

// Compilation error: Cannot inherit from 'Base'
class Derived : public Base {  
};

Key takeaway: A final class cannot be used as a base class.

Preventing Virtual Function Overriding

class Base {
public:
    virtual void display() final {  // 'final' prevents overriding
        std::cout << "Base display function\n";
    }
};

class Derived : public Base {
public:
    // Compilation error: Cannot override 'display' because it is final
    void display() override {  
        std::cout << "Derived display function\n";
    }
};

Key takeaway: A final function cannot be overridden in derived classes. Any attempt to override a final function results in a compile-time error.

C++ final: Under-the-Hood Implementation

Internally, final is enforced at compile time by the compiler’s type-checking mechanism. It does not add runtime overhead but helps the compiler generate more optimized code.

Some compilers may use final to aid in devirtualization, optimizing virtual function calls by removing vtable lookups.

C++ final: Performance Optimization

When using final, the compiler annotates the class or function with flags that indicate it cannot be overridden. If a derived class attempts to violate final rules, the compiler throws an error. While final is primarily a design tool, it can impact program performance in various ways. However, its effectiveness depends on compiler behavior and specific scenarios.

Enabling Devirtualization

Virtual function calls usually require vtable lookups, adding runtime overhead. When a function is marked final, the compiler knows it cannot be overridden. This allows for devirtualization, where the compiler replaces a virtual function call with a direct call.

Example: Devirtualization Optimization

class Base {
public:
    virtual void process() final {  // 'final' helps compiler optimize
        std::cout << "Processing in Base\n";
    }
};

void execute(Base& obj) {
    obj.process();  // Compiler may optimize this into a direct call
}

Expected Behavior: Some compilers inline the function call, skipping the vtable lookup, which improves performance. While this behavior may be “expected”, it may not be the case. In this simple example, GCC creates a much simpler program WITHOUT the use of final:

Using final in the above example generates this assembly:

.LC0:
        .string "Processing in Base\n"
execute(Base&):
        sub     rsp, 8
        mov     edx, 19
        mov     esi, OFFSET FLAT:.LC0
        mov     edi, OFFSET FLAT:std::cout
        call    std::basic_ostream<char, std::char_traits<char> >& std::__ostream_insert<char, std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*, long)
        add     rsp, 8
        ret

Removing final in the above example generates this assembly:

execute(Base&):
        sub     rsp, 8
        mov     rax, QWORD PTR [rdi]
        call    [QWORD PTR [rax]]
        add     rsp, 8
        ret

Play with this for yourself in Compiler Explorer.

Compiler-Specific Performance Variations

Recent research shows that the impact of final on performance is highly dependent on the compiler:

  • GCC & MSVC: Show potential improvements when final is applied.
  • Clang: In some cases, applying final degraded performance due to unexpected compiler behavior.

According to Benjamin Summerton’s analysis, when final was applied across interfaces in a ray tracing project, Clang-generated code became slower, while MSVC and GCC produced mixed results.

The benchmarks involved a ray tracing project tested on three machines with different operating systems and compilers, both with and without the final keyword applied.

Key Findings:

  • GCC (GNU Compiler Collection): Generally exhibited performance improvements when the final keyword was used. For instance, on an AMD Ryzen 9 6900HX running Ubuntu 23.10 with GCC 13.2.0, certain test cases showed up to a 10% performance boost.
  • Clang: Demonstrated significant performance degradation with the application of final. On the same AMD system using Clang 17.0.2, over 90% of test cases experienced at least a 5% slowdown, with some cases worsening by up to 17%.
  • MSVC (Microsoft Visual C++): Presented mixed results. On an AMD Ryzen 9 6900HX running Windows 11 with MSVC 17, some scenes benefited from the final keyword, while others suffered performance hits.
  • Apple Silicon (M1) with GCC and Clang: Showed minimal performance changes, with both gains and losses being relatively small.

On Reddit discussions, developers also highlighted that final does not always guarantee performance gains, and in some cases, excessive use may negatively affect optimizations.

Using C++ final to Reduce Binary Size

By preventing overrides, final helps reduce the number of virtual function entries in the vtable, leading to smaller binaries. This can be beneficial in embedded systems or performance-critical applications. Since final prevents further inheritance and overriding, unnecessary virtual function entries are not added to the vtable. This leads to a smaller binary size and potentially faster execution.

Using C++ final to Improve Branch Prediction

Since final ensures that a function call target never changes, the processor can better predict execution flow, leading to improved CPU branch prediction. Removing vtable lookups reduces indirect jumps, which helps branch prediction work more efficiently.

Microsoft’s Performance Analysis of C++ final

Microsoft’s C++ Team Blog highlights scenarios where final allowed the compiler to inline virtual function calls, leading to noticeable speed improvements.

However, as with any optimization, testing and profiling are necessary to confirm actual benefits in a given application.

When NOT to Use final for Performance

  1. If the compiler does not optimize final correctly, it may not improve performance.
  2. If a class is meant to be extended in the future, using final limits flexibility.
  3. Excessive use may lead to unintended side effects on compiler optimizations.

Common Misconceptions About C++ final

The final keyword in C++ is often misunderstood, especially regarding its impact on performance and class design. Below are some common misconceptions and the facts that debunk them.

❌ Misconception 1: final Always Improves Performance

Fact: The performance impact of final depends on the compiler and code context.

  • GCC and MSVC may optimize virtual function calls using final, enabling devirtualization.
  • Clang, in some cases, has shown performance degradation when final is applied.
  • If the function is already inlined or not frequently called, final has no measurable performance impact.

🔹 Takeaway: Always profile your code before assuming final will speed it up.

❌ Misconception 2: final Eliminates the vtable Entry

Fact: final prevents overriding, but it does not necessarily remove the vtable entry.

  • A final function in a class with other virtual functions still requires a vtable.
  • Compilers may optimize out the vtable lookup, but the function still exists in the table.
  • If the base class is not used polymorphically, then the compiler may eliminate the vtable entirely.

🔹 Takeaway: final can help with devirtualization, but the vtable isn’t always removed.

❌ Misconception 3: final Prevents Function Inlining

Fact: final actually encourages function inlining because the compiler knows the function cannot be overridden.

  • Virtual functions normally prevent inlining because their calls are resolved at runtime.
  • When a function is marked final, the compiler may replace it with a direct function call and inline it.

🔹 Takeaway: If performance is a concern, check the compiler-generated assembly output to verify inlining.

❌ Misconception 4: final is the Same as sealed in Other Languages

Fact: final is similar but more flexible than sealed in C# or Java.

  • In C# and Java, sealed applies only to classes.
  • In C++, final applies to both classes and virtual functions.
  • Unlike Java, C++ does not enforce inheritance rules by default, so final gives developers explicit control.

🔹 Takeaway: C++’s final is more fine-grained, allowing you to control both class inheritance and function overriding.

❌ Misconception 5: final Prevents Object Instantiation

Fact: A final class can still be instantiated—it just cannot be inherited from.

class Singleton final {
public:
    static Singleton& getInstance() {
        static Singleton instance;
        return instance;
    }
};

Singleton obj;  // ✅ This is perfectly valid
  • final only affects inheritance and does not change how objects are created.
  • If you want to prevent instantiation, you need to delete the constructor.

🔹 Takeaway: final restricts inheritance, not object creation.

❌ Misconception 6: final Makes Code Less Maintainable

Fact: final improves maintainability by preventing unintended modifications.

  • It helps lock down core library classes that should not be extended.
  • It enforces clear design intent, reducing bugs from accidental inheritance.
  • However, overusing final can reduce flexibility, so use it only when necessary.

🔹 Takeaway: final is a tool for enforcing design constraints, but should not be applied blindly.

FAQs

Q1: Can final be used on non-virtual functions?

No, it only applies to virtual functions. Using final on a non-virtual function has no effect.

Q2: Does final always improve performance?

No. While it may enable optimizations, real-world results depend on the compiler.

Q3: Should I add final everywhere for performance?

No. Only use it when:

  1. You want to prevent overriding.
  2. You observe actual performance gains in profiling.

Q4: Can a final class be instantiated?

Yes, marking a class final only prevents inheritance, but objects of the class can still be created.

Q5: How does final compare to sealed in C#?

Similar to sealed in C#, but in C++ it also applies to virtual functions, not just classes.

Quick Reference

FeatureEffect of final
Prevents Inheritance✅ Yes, a final class cannot be subclassed
Prevents Overriding✅ Yes, a final function cannot be overridden
Performance Impact⚠️ Compiler-dependent; can improve or degrade performance
Devirtualization✅ Helps compilers remove vtable lookups
Inlining✅ Encourages function inlining in some cases
Code Maintainability✅ Ensures intended inheritance structure

Best Practices

The C++ final keyword is a useful tool for both design and potential performance optimization. While it enforces stricter class hierarchies, it can also help the compiler generate faster, more optimized code. However, real-world performance benefits vary based on compiler behavior, so always profile before applying it as an optimization strategy.

Many developers assume that final is just for performance optimizations, but its primary role is to enforce class and function constraints. It can help with optimizations like devirtualization and inlining, but results vary by compiler and use case.

✅ When to Use final

  • To prevent unintended inheritance.
  • To allow the compiler to optimize virtual function calls.
  • Use final on classes that should not be extended (e.g., core framework classes).
  • Use final on performance-critical virtual functions to allow devirtualization.
  • Use final to enforce design decisions and prevent unintended overrides.
  • In performance-critical code where devirtualization is beneficial.
  • To support Data-Driven Decision-Making regarding program optimization.

❌ When NOT to Use final

  • Avoid final on non-virtual functions (it has no effect).
  • Avoid final on classes meant to be extended in the future.
  • Avoid excessive use—it might reduce code flexibility.
  • If the class is designed for future extension.
  • If the compiler does not optimize it well (especially with Clang).
  • If final reduces code readability or maintainability.

Have you seen final speed up your C++ code? Share your benchmarks below!

Do you think final is overused? Let’s discuss in the comments!


Discover more from John Farrier

Subscribe to get the latest posts sent to your email.

2 thoughts on “The Definitive Guide to the C++ final Keyword

    1. Thank you for the correction. While I use both C# and Java, I am certainly not as deep into their languages as I am with C++!

Leave a Reply

Discover more from John Farrier

Subscribe now to keep reading and get access to the full archive.

Continue reading