Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

C++11 final Keyword

Table of Contents


What is the final Keyword?

The final keyword, introduced in C++11, is used to restrict inheritance and method overriding. It can be applied in two contexts:

  1. Final Classes - Prevents a class from being inherited
  2. Final Methods - Prevents a virtual method from being overridden in derived classes

Preventing Class Inheritance

When a class is marked as final, no other class can inherit from it.

Syntax:

class ClassName final {
    // Class definition
};

Example:

#include <iostream>
using namespace std;

// This class cannot be inherited
class FinalClass final {
public:
    void display() {
        cout << "This is a final class" << endl;
    }
};

// Attempting to inherit from FinalClass
class DerivedClass : public FinalClass {  // ERROR: Cannot inherit from final class
public:
    void show() {
        cout << "Derived class" << endl;
    }
};

int main() {
    FinalClass obj;
    obj.display();
    return 0;
}

Compiler Error:

error: cannot derive from 'final' base 'FinalClass' in derived type 'DerivedClass'

Preventing Method Override

When a virtual method is marked as final, derived classes cannot override it.

Syntax:

virtual return_type methodName() final {
    // Method implementation
}

Example:

#include <iostream>
using namespace std;

class Base {
public:
    virtual void display() {
        cout << "Base display" << endl;
    }
    
    // This method cannot be overridden
    virtual void show() final {
        cout << "Base show - cannot be overridden" << endl;
    }
};

class Derived : public Base {
public:
    // This is allowed
    void display() override {
        cout << "Derived display" << endl;
    }
    
    // This will cause a compilation error
    void show() override {  // ERROR: Cannot override final method
        cout << "Derived show" << endl;
    }
};

int main() {
    Derived obj;
    obj.display();
    obj.show();
    return 0;
}

Compiler Error:

error: virtual function 'virtual void Derived::show()' overrides final function

⬆ Back to Table of Contents


How Programmers Achieved This Before C++11

Before C++11, there was no direct language support for preventing inheritance or method overriding. Programmers used various workarounds, all with significant limitations.

Private/Protected Constructor Approach

One common technique was to make constructors private or protected, preventing direct instantiation of derived classes.

#include <iostream>
using namespace std;

class NonInheritableClass {
private:
    NonInheritableClass() {  // Private constructor
        cout << "NonInheritableClass created" << endl;
    }
    
public:
    // Factory method for creating instances
    static NonInheritableClass* create() {
        return new NonInheritableClass();
    }
    
    void display() {
        cout << "Display method" << endl;
    }
};

// Attempting to inherit
class DerivedClass : public NonInheritableClass {
public:
    DerivedClass() {  // ERROR: Cannot access private constructor
        cout << "Derived class" << endl;
    }
};

int main() {
    // Cannot create object directly
    // NonInheritableClass obj;  // ERROR
    
    // Must use factory method
    NonInheritableClass* obj = NonInheritableClass::create();
    obj->display();
    delete obj;
    
    return 0;
}

Friend Class Approach

Another technique combined private constructors with friend classes for controlled creation.

#include <iostream>
using namespace std;

class NonInheritableClass;

// Helper class that can create NonInheritableClass
class Creator {
public:
    static NonInheritableClass* create();
};

class NonInheritableClass {
private:
    NonInheritableClass() {
        cout << "Created via friend" << endl;
    }
    
    friend class Creator;  // Only Creator can access private constructor
    
public:
    void display() {
        cout << "Display method" << endl;
    }
};

NonInheritableClass* Creator::create() {
    return new NonInheritableClass();
}

int main() {
    NonInheritableClass* obj = Creator::create();
    obj->display();
    delete obj;
    
    return 0;
}

Problems with Pre-C++11 Approaches

These workarounds had several significant issues:

1. No Direct Method Override Prevention

class Base {
public:
    virtual void criticalMethod() {
        // Important logic that shouldn't be changed
    }
};

class Derived : public Base {
public:
    // No way to prevent this override before C++11
    void criticalMethod() override {
        // Oops! Accidentally overridden
    }
};

2. Complex and Error-Prone Code

// Required complex boilerplate code
class SafeClass {
private:
    SafeClass() {}
    static SafeClass* instance;
    
public:
    static SafeClass* getInstance() {
        if (!instance) {
            instance = new SafeClass();
        }
        return instance;
    }
    // Lots of additional code needed...
};

SafeClass* SafeClass::instance = nullptr;

3. Unclear Intent

// Why is the constructor private? To prevent inheritance or for Singleton pattern?
class MyClass {
private:
    MyClass() {}  // Intent is not clear
    
public:
    static MyClass* create() {
        return new MyClass();
    }
};

4. Memory Management Burden

// Forced to use pointers and factory methods
MyClass* obj = MyClass::create();
obj->doSomething();
delete obj;  // Must remember to delete

// Could not simply do:
// MyClass obj;  // Direct instantiation not possible

5. Incomplete Prevention

class Base {
private:
    Base() {}
    
public:
    static Base create() {
        return Base();
    }
};

// This still compiles in some cases!
class Derived : public Base {
    // Can still inherit even with private constructor
};

⬆ Back to Table of Contents


How final Keyword Improved the Code

The final keyword provides a clean, explicit, and reliable solution that addresses all the problems of previous approaches.

Clear Intent

The final keyword makes the programmer’s intent immediately obvious.

Before C++11:

class Configuration {
private:
    Configuration() {}  // Why private? Not immediately clear
    
public:
    static Configuration* getInstance();
    void setOption(string key, string value);
};

With final:

class Configuration final {
public:
    Configuration() {}  // Clear: this class cannot be inherited
    void setOption(string key, string value);
};

Compile-Time Enforcement

The compiler enforces the restriction, catching errors early.

class SecurityManager final {
public:
    void authenticate(string username, string password) {
        // Critical security logic
    }
};

// Compiler immediately catches this error
class CustomSecurityManager : public SecurityManager {  // COMPILE ERROR
    // Cannot compromise security by inheriting
};

Better Error Messages

Clear, understandable compiler errors help developers fix issues quickly.

Example:

class ImmutableString final {
    string data;
public:
    ImmutableString(string s) : data(s) {}
    string get() const { return data; }
};

class MutableString : public ImmutableString {  // ERROR
public:
    void set(string s) { /* ... */ }
};

Compiler Error:

error: cannot derive from 'final' base 'ImmutableString'

This is much clearer than cryptic errors about private constructors!

Performance Optimizations

The compiler can make optimization decisions knowing that methods won’t be overridden.

class FastMath {
public:
    virtual int add(int a, int b) final {
        return a + b;
    }
    
    virtual int multiply(int a, int b) final {
        return a * b;
    }
};

// Compiler knows these methods are final and can:
// - Inline them more aggressively
// - Skip virtual table lookups
// - Apply devirtualization optimizations

Comparison Example:

#include <iostream>
#include <chrono>
using namespace std;

class NonFinalClass {
public:
    virtual int compute(int x) {
        return x * x;
    }
};

class FinalClass {
public:
    virtual int compute(int x) final {
        return x * x;
    }
};

int main() {
    NonFinalClass nfc;
    FinalClass fc;
    
    const int iterations = 100000000;
    
    // Non-final method call
    auto start = chrono::high_resolution_clock::now();
    int sum1 = 0;
    for(int i = 0; i < iterations; i++) {
        sum1 += nfc.compute(i);
    }
    auto end = chrono::high_resolution_clock::now();
    auto duration1 = chrono::duration_cast<chrono::milliseconds>(end - start);
    
    // Final method call (potentially optimized)
    start = chrono::high_resolution_clock::now();
    int sum2 = 0;
    for(int i = 0; i < iterations; i++) {
        sum2 += fc.compute(i);
    }
    end = chrono::high_resolution_clock::now();
    auto duration2 = chrono::duration_cast<chrono::milliseconds>(end - start);
    
    cout << "Non-final time: " << duration1.count() << "ms" << endl;
    cout << "Final time: " << duration2.count() << "ms" << endl;
    
    return 0;
}

Simplified Code Structure

No need for complex workarounds or boilerplate code.

Before C++11 (50+ lines):

class Singleton {
private:
    static Singleton* instance;
    Singleton() {}
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;
    
public:
    static Singleton* getInstance() {
        if (!instance) {
            instance = new Singleton();
        }
        return instance;
    }
    
    void doWork() {
        cout << "Working..." << endl;
    }
};

Singleton* Singleton::instance = nullptr;

// Usage requires pointers
Singleton* obj = Singleton::getInstance();
obj->doWork();

With final (10 lines):

class Singleton final {
private:
    Singleton() {}
    
public:
    static Singleton& getInstance() {
        static Singleton instance;
        return instance;
    }
    
    void doWork() {
        cout << "Working..." << endl;
    }
};

// Usage is cleaner
Singleton::getInstance().doWork();

⬆ Back to Table of Contents


When to Use final?

Use Cases for final Classes

1. Utility Classes with Static Methods

Classes that only contain static helper functions should be final.

class MathUtils final {
public:
    static double sqrt(double x) {
        // Implementation
        return 0.0;
    }
    
    static double pow(double base, double exp) {
        // Implementation
        return 0.0;
    }
    
    // No need for inheritance - just utility functions
};

2. Value Objects / Data Transfer Objects (DTOs)

Simple data containers that represent immutable values.

class Point final {
private:
    int x, y;
    
public:
    Point(int x, int y) : x(x), y(y) {}
    
    int getX() const { return x; }
    int getY() const { return y; }
    
    // No need to extend - it's just a point
};

3. Implementation Classes (Not Interfaces)

Concrete implementations that should not be further specialized.

class HttpClient final {
public:
    void sendRequest(string url) {
        // Concrete implementation
        cout << "Sending HTTP request to " << url << endl;
    }
    
    string receiveResponse() {
        // Concrete implementation
        return "Response data";
    }
};

4. Security-Critical Classes

Classes where inheritance could compromise security or correctness.

class PasswordHasher final {
public:
    string hash(string password) {
        // Critical hashing algorithm
        // Must not be altered by inheritance
        return "hashed_password";
    }
    
    bool verify(string password, string hash) {
        // Critical verification logic
        return true;
    }
};

Use Cases for final Methods

1. Template Method Pattern - Fixed Steps

When certain steps in an algorithm must never change.

class DataProcessor {
public:
    // Template method defines the algorithm
    void process() {
        readData();
        validateData();  // This step is fixed
        transformData(); // This can be customized
        writeData();     // This step is fixed
    }
    
protected:
    virtual void readData() {
        cout << "Reading data..." << endl;
    }
    
    // This validation must always happen exactly this way
    virtual void validateData() final {
        cout << "Performing mandatory validation..." << endl;
        // Critical validation logic that must not be changed
    }
    
    virtual void transformData() = 0;  // Subclasses must implement
    
    // Writing must follow specific protocol
    virtual void writeData() final {
        cout << "Writing data with integrity checks..." << endl;
        // Must not be altered
    }
};

class CSVProcessor : public DataProcessor {
protected:
    void transformData() override {
        cout << "Converting to CSV format..." << endl;
    }
    
    // Cannot override validateData() or writeData() - they are final
};

2. Performance-Critical Methods

Methods that are optimized and should not be overridden.

class GraphicsRenderer {
public:
    // Highly optimized rendering code
    virtual void render() final {
        // Assembly-optimized or GPU-accelerated code
        // Must not be overridden to maintain performance
        cout << "Optimized rendering..." << endl;
    }
    
    virtual void setColor(int r, int g, int b) {
        // Can be overridden
    }
};

3. Preventing Accidental Override

Methods that work correctly and should not be accidentally broken.

class BankAccount {
protected:
    double balance;
    
public:
    BankAccount(double initial) : balance(initial) {}
    
    virtual void deposit(double amount) {
        if(amount > 0) {
            balance += amount;
        }
    }
    
    // Critical business logic - must not be changed
    virtual bool withdraw(double amount) final {
        if(amount > 0 && balance >= amount) {
            balance -= amount;
            return true;
        }
        return false;
    }
    
    double getBalance() const {
        return balance;
    }
};

class SavingsAccount : public BankAccount {
public:
    SavingsAccount(double initial) : BankAccount(initial) {}
    
    // Can add interest calculation
    void addInterest(double rate) {
        deposit(balance * rate);
    }
    
    // Cannot override withdraw() - protected by final
};

4. Ensuring Contract Compliance

When a method implements a critical contract that must be maintained.

class Observable {
private:
    vector<Observer*> observers;
    
public:
    void attach(Observer* obs) {
        observers.push_back(obs);
    }
    
    // Notification must always work this way
    virtual void notify() final {
        for(auto obs : observers) {
            obs->update(this);
        }
    }
    
    virtual void setState(int state) {
        // Can be overridden
    }
};

When NOT to Use final

1. Library/Framework Base Classes

Classes designed to be extended by users.

// DON'T do this
class Widget final {  // BAD - users might want to extend
public:
    virtual void render();
};

// DO this instead
class Widget {
public:
    virtual void render();
    virtual ~Widget() {}
};

2. When Extensibility is a Feature

Classes that are meant to be customized.

// DON'T do this
class Plugin final {  // BAD - plugins need to be extended
public:
    virtual void execute();
};

// DO this instead
class Plugin {
public:
    virtual void execute() = 0;
    virtual ~Plugin() {}
};

3. Early in Development

Don’t use final prematurely before the design stabilizes.

// During prototyping - keep it flexible
class GameEntity {
public:
    virtual void update();
    virtual void render();
};

// Later, when design is stable, you might make specific methods final
class GameEntity {
public:
    virtual void update();
    virtual void render() final;  // Now we know this shouldn't change
};

4. When Testing Requires Mocking

Classes that need to be mocked for unit testing.

// DON'T do this if you need to mock
class DatabaseConnection final {  // BAD - cannot mock for testing
public:
    void query(string sql);
};

// DO this instead
class DatabaseConnection {
public:
    virtual void query(string sql);
    virtual ~DatabaseConnection() {}
};

// Now you can create a mock for testing
class MockDatabaseConnection : public DatabaseConnection {
public:
    void query(string sql) override {
        // Mock implementation for testing
    }
};

⬆ Back to Table of Contents


Best Practices and Guidelines

  1. Use final conservatively - Only use it when you have a clear reason to prevent inheritance or overriding

  2. Document why - Add comments explaining why a class or method is final

    // Final to prevent security vulnerabilities through inheritance
    class AuthenticationManager final {
        // ...
    };
    
  3. Combine with override - When marking a method final, use both keywords for clarity

    class Derived : public Base {
    public:
        void method() override final {  // Both override and final
            // ...
        }
    };
    
  4. Consider alternatives - Sometimes composition is better than preventing inheritance

    // Instead of making everything final
    class FinalClass final {
        void doWork();
    };
    
    // Consider composition
    class Worker {
        Helper helper;  // Use composition instead
    public:
        void doWork() {
            helper.assist();
        }
    };
    
  5. Virtual destructors - If a class has virtual methods, ensure it has a virtual destructor

    class Base {
    public:
        virtual void method() final;
        virtual ~Base() {}  // Virtual destructor
    };
    
  6. Performance considerations - Use final on hot-path methods to enable compiler optimizations

    class FastProcessor {
    public:
        virtual int compute(int x) final {
            return x * x;  // Can be inlined aggressively
        }
    };
    
  7. API design - For public APIs, think carefully before using final as it limits users

  8. Team communication - Discuss with team before making classes final in shared codebases

⬆ Back to Table of Contents