
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:
- Preventing Class Inheritance: Marking a class as
final
stops it from being used as a base class. - 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
- If the compiler does not optimize
final
correctly, it may not improve performance. - If a class is meant to be extended in the future, using
final
limits flexibility. - 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:
- You want to prevent overriding.
- 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
Feature | Effect 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.
Thanks for the article.
Small correction: “In C# and Java, sealed applies only to classes.” The sentence is not true.
C# has sealed keyword and the keyword can be applied to methods. See the doc. https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/sealed
I don’t use Java, but Java also seems to support final keyword for methods. https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/sealed
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++!