skip to content
Mehdi Mehdikhani
Table of Contents

The final specifier in C++11 lets you explicitly prevent inheritance or virtual function overriding. It’s great for enforcing design decisions and catching mistakes.

What final Actually Does

final can be used in two ways:

  1. On classes: Prevents the class from being inherited
  2. On virtual functions: Prevents the function from being overridden further
class Base {
public:
virtual void func() = 0;
};
class Derived final : public Base { // Cannot be inherited
public:
void func() override final; // Cannot be overridden
};
// class Error : public Derived {}; // Compilation error

Why This Is Useful

Before final, there was no way to explicitly prevent inheritance. You had to rely on documentation or complex workarounds:

// The old way - hacky and unclear
class NonInheritable {
friend class ActualClass;
private:
NonInheritable() {}
};
class ActualClass : virtual NonInheritable {
public:
ActualClass() {}
};

With final, intent is clear and enforced by the compiler.

When I Actually Use final

Most of the time, I use final in these situations:

  1. Leaf classes: When a class represents a complete, final implementation:
class FileLogger final : public Logger {
public:
void log(const std::string& message) override {
file_ << message << std::endl;
}
private:
std::ofstream file_;
};
  1. Performance-critical classes: Where I want to enable compiler optimizations:
class Vector3D final {
float x_, y_, z_;
public:
float dot(const Vector3D& other) const {
return x_ * other.x_ + y_ * other.y_ + z_ * other.z_;
}
// Compiler can optimize knowing this won't be inherited
};
  1. Preventing further overriding: When a virtual function implementation is complete:
class Shape {
public:
virtual double area() const = 0;
virtual void draw() const = 0;
};
class Rectangle : public Shape {
public:
double area() const override {
return width_ * height_;
}
void draw() const override final { // Complete implementation
// Drawing logic that shouldn't be changed
}
private:
double width_, height_;
};
  1. Security or stability: When inheritance could break invariants:
class CryptoHash final { // Inheritance could compromise security
public:
std::string hash(const std::string& input) const {
// Secure hashing implementation
}
};

The Design Benefits

Using final makes design intent explicit:

// Clear hierarchy design
class Animal {
public:
virtual void make_sound() const = 0;
};
class Mammal : public Animal {
public:
virtual void breathe() const {
std::cout << "Breathing air" << std::endl;
}
};
class Dog final : public Mammal { // Dogs are concrete animals
public:
void make_sound() const override final {
std::cout << "Woof!" << std::endl;
}
};

Performance Implications

final classes enable compiler optimizations:

class OptimizedClass final {
public:
void hot_function() const {
// Compiler can inline this more aggressively
// because it knows no derived classes exist
}
};

The compiler knows there are no derived classes, so it can eliminate virtual function call overhead in some cases.

Common Patterns

I often see these patterns:

  1. Value types: Mathematical or utility classes:
class Point final {
double x_, y_;
public:
Point(double x, double y) : x_(x), y_(y) {}
// Mathematical operations...
};
  1. RAII wrappers: Resource management classes:
class FileHandle final {
FILE* file_;
public:
explicit FileHandle(const std::string& filename)
: file_(fopen(filename.c_str(), "r")) {}
~FileHandle() {
if (file_) fclose(file_);
}
// No copying or inheritance
FileHandle(const FileHandle&) = delete;
FileHandle& operator=(const FileHandle&) = delete;
};

The Rule I Follow

Use final when:

  • The class represents a complete, concrete implementation
  • Inheritance would break the class’s design or invariants
  • Performance is critical and you want to enable optimizations
  • You want to make design intent explicit

Don’t use final when:

  • The class might reasonably be extended in the future
  • You’re building a library where users might want to inherit
  • The benefits aren’t clear

final is about being explicit with design decisions. It documents intent and lets the compiler help enforce it.