The final Keyword

Definition: final prevents further derivation or further overriding. Use it to lock a design decision and communicate that a hierarchy is closed.


Purpose and Benefits

The final keyword serves two main purposes:

🚫 Prevent Class Inheritance

Ensures a class cannot be used as a base class

🔒 Prevent Method Overriding

Ensures a virtual method cannot be overridden in derived classes

Benefits:

Design Clarity

Explicitly communicates intent

Safety

Prevents unintended inheritance

Optimization

Enables compiler optimizations (devirtualization)

API Stability

Locks implementation details

Documentation

Self-documenting code


Prevent Class Inheritance

Syntax: Place final after the class name and base class list.

Listing 36 Final Class Example
class RoboTaxi final : public Vehicle {
    // RoboTaxi implementation
};

// ERROR: cannot inherit from final class
// class SuperRoboTaxi : public RoboTaxi {};

When to Use final Classes

Use final for classes when:

✓ The class is a leaf node in the inheritance hierarchy

✓ Further specialization would violate the design

✓ The class manages resources in a way that would break with inheritance

✓ You want to prevent the fragile base class problem

✓ Performance is critical (enables devirtualization)

Listing 37 Complete, Self-Contained Type
// String is a complete, self-contained type
class String final {
private:
    char* data_;
    size_t size_;
public:
    String(const char* str);
    ~String();
    // No meaningful way to extend String
};
Listing 38 Internal Implementation
// Internal implementation that shouldn't be extended
class DatabaseConnectionImpl final : public DatabaseConnection {
private:
    void* native_handle_;
public:
    void connect() override;
    void disconnect() override;
    // Inheritance would break invariants
};

Prevent Method Overriding

Syntax: Place final after the method signature, before the method body.

Listing 39 Final Method Example
class Vehicle {
public:
    virtual ~Vehicle() = default;
    virtual void drive() = 0;

    // Cannot be overridden in derived classes
    virtual void move() final {
        /* fixed behavior */
    }
};

class RoboTaxi : public Vehicle {
public:
    void drive() override { /* OK */ }

    // ERROR: cannot override final method
    // void move() override { }
};

When to Use final Methods

Use final for methods when:

✓ The method’s implementation must not change in derived classes

✓ Overriding would violate class invariants

✓ The method implements a critical algorithm or security check

✓ The base implementation is performance-critical

✓ You want to enforce the Template Method pattern

Listing 40 Security Check Must Not Be Bypassed
class SecureConnection {
public:
    virtual ~SecureConnection() = default;

    // Security check must not be bypassed
    virtual bool authenticate(const Credentials& creds) final {
        return verify_signature(creds) && check_permissions(creds);
    }

    // Derived classes can implement transport
    virtual void send(const Data& data) = 0;
};
Listing 41 Fixed Algorithm Structure
class Algorithm {
public:
    // Template method - fixed algorithm structure
    void execute() final {
        initialize();
        process();
        finalize();
    }

protected:
    // Customization points
    virtual void initialize() = 0;
    virtual void process() = 0;
    virtual void finalize() = 0;

    virtual ~Algorithm() = default;
};

Combining final and override

A method can be both override and final:

class Base {
public:
    virtual ~Base() = default;
    virtual void foo() = 0;
};

class Middle : public Base {
public:
    // Overrides Base::foo and prevents further overriding
    void foo() override final {
        // Implementation
    }
};

class Derived : public Middle {
public:
    // ERROR: cannot override final method
    // void foo() override { }
};

Note

This pattern is useful when you want to provide a definitive implementation in a middle class of a hierarchy.


Design Considerations

When to Use final

✅ Good Reasons to Use final

Leaf classes: The class is a complete implementation with no meaningful extensions

Security: Prevent tampering with security-critical methods

Invariants: Overriding would break class invariants

Performance: Enable devirtualization in hot paths

API contract: Lock implementation details in a stable API

❌ When NOT to Use final

The design might evolve and need extension

You’re creating a library that users might want to extend

There’s no clear reason to prevent inheritance

You’re prematurely optimizing

Example: Complete Implementation
// No meaningful extensions possible
class MD5Hash final {
public:
    std::array<uint8_t, 16> compute(const std::vector<uint8_t>& data);
};
Example: Fixed Behavior
class Transaction {
public:
    // Must execute in this exact order
    void execute() final {
        begin();
        perform_operation();
        commit();
    }

protected:
    virtual void perform_operation() = 0;
};
Example: Premature Finalization (Bad)
// Bad - prevents potentially useful extensions
class Button final : public Widget {
    // What if someone wants RoundedButton or IconButton?
};
Better Approach
// Allow extension by default
class Button : public Widget {
    // Derived classes can extend if needed
};

// Only finalize when there's a clear reason
class SystemButton final : public Button {
    // OS-specific implementation that shouldn't be extended
};

Performance Implications

final can enable compiler optimizations:

Devirtualization

Compiler can replace virtual calls with direct calls

Inlining

Direct calls can be inlined for better performance

Dead Code Elimination

Unused virtual methods can be removed

Listing 42 Devirtualization Example
class Base {
public:
    virtual void foo() { /* ... */ }
};

class Derived final : public Base {
public:
    void foo() override { /* ... */ }
};

void call_foo(Derived* d) {
    d->foo();  // Can be devirtualized because Derived is final
}

Warning

Don’t use final solely for performance without profiling. The optimization benefits are often minimal, and premature finalization limits flexibility.


Common Patterns

Listing 43 Final Implementation in Hierarchy
class Shape {
public:
    virtual ~Shape() = default;
    virtual double area() const = 0;
    virtual void draw() const = 0;
};

class Polygon : public Shape {
public:
    virtual std::vector<Point> vertices() const = 0;
};

// Final implementation - no need to extend further
class Triangle final : public Polygon {
public:
    double area() const override { /* ... */ }
    void draw() const override { /* ... */ }
    std::vector<Point> vertices() const override { /* ... */ }
};
Listing 44 Template Method with Customization Points
class DataProcessor {
public:
    // Fixed processing pipeline
    void process(const Data& input) final {
        if (!validate(input)) return;
        auto transformed = transform(input);
        store(transformed);
    }

protected:
    virtual bool validate(const Data& input) = 0;
    virtual Data transform(const Data& input) = 0;
    virtual void store(const Data& output) = 0;

    virtual ~DataProcessor() = default;
};
Listing 45 Complete Value-Semantic Type
// Complete value type - no meaningful inheritance
class UUID final {
private:
    std::array<uint8_t, 16> bytes_;
public:
    UUID();
    explicit UUID(const std::string& str);

    std::string to_string() const;
    bool operator==(const UUID& other) const;
    bool operator<(const UUID& other) const;
};
Listing 46 Private Implementation Classes
// Public interface
class HttpClient {
public:
    virtual ~HttpClient() = default;
    virtual Response get(const std::string& url) = 0;
    virtual Response post(const std::string& url, const Data& body) = 0;
};

// Internal implementation - shouldn't be extended
class HttpClientImpl final : public HttpClient {
private:
    void* curl_handle_;
public:
    Response get(const std::string& url) override;
    Response post(const std::string& url, const Data& body) override;
};

Best Practices

1. Be Conservative

Default to allowing extension. Only use final when there’s a clear reason.

2. Finalize Leaf Classes

Use final for concrete implementations in closed hierarchies.

class PngImage final : public Image { };
class JpegImage final : public Image { };
3. Fixed Algorithms

Use final methods for algorithms that must not change.

bool authenticate(const User& u) final {
    return check_password(u) &&
           check_permissions(u);
}
4. Document Why

Always document the reason for using final.

// final: MD5 is a fixed algorithm
class MD5Hasher final {
    // ...
};

Common Mistakes

❌ Mistake 1: Finalizing Too Early
// Bad - prevents potentially useful extensions
class Vehicle final {
    // What about RoboTaxi, Taxi, Bus, etc.?
};

Fix: Only finalize leaf nodes, not intermediate abstractions.

❌ Mistake 2: Mixing final with Pure Virtual
class Base {
public:
    // ERROR: final and pure virtual are contradictory
    // virtual void foo() final = 0;
};

A pure virtual method must be overridden, so it cannot be final.

❌ Mistake 3: Using final for Optimization Without Profiling
// Questionable - optimizing before measuring
class MyClass final {
    // Is this really a hot path?
};

Better: Profile first, optimize with final only if it matters.


Core Concepts:

  • final prevents class inheritance or method overriding

  • Use for leaf classes in closed hierarchies

  • Use for fixed algorithms that shouldn’t change

  • Use for security-critical methods

  • Don’t overuse - allow extension by default

  • Enables compiler optimizations (devirtualization)

  • Can combine with override

  • Cannot combine with pure virtual (= 0)

  • Document why something is final

  • Profile before using final for performance