C++ Override and Final Specifiers
Introduction
The override and final specifiers were introduced in C++11 to help developers write more robust code when dealing with inheritance and virtual functions. These specifiers provide explicit control over method overriding in derived classes, making code more readable and preventing common bugs related to inheritance.
In this tutorial, you'll learn:
- What the
overridespecifier does and how to use it - What the
finalspecifier does for both methods and classes - How these specifiers can prevent subtle bugs in your code
- Best practices when working with inheritance hierarchies
The Override Specifier
What Problem Does override Solve?
In C++, when you inherit from a base class, you can override virtual methods to provide different implementations in derived classes. However, before C++11, it was easy to accidentally create a new method instead of overriding an existing one, as shown in the example below:
class Base {
public:
virtual void show() {
std::cout << "Base class" << std::endl;
}
};
class Derived : public Base {
public:
// Oops! This is a new method, not an override
// The method signature doesn't match exactly (missing const)
virtual void show() const {
std::cout << "Derived class" << std::endl;
}
};
int main() {
Derived d;
Base* b = &d;
b->show(); // Calls Base::show(), not Derived::show()
return 0;
}
Output:
Base class
In the above example, the developer intended to override show(), but accidentally created a new method because the signatures don't match exactly (one has const and the other doesn't).
Using the override Specifier
The override specifier tells the compiler that a method is intended to override a virtual method from a base class. If it doesn't actually override anything, the compiler will generate an error:
class Base {
public:
virtual void show() {
std::cout << "Base class" << std::endl;
}
};
class Derived : public Base {
public:
// Compiler error: no virtual method in base class matches this signature
void show() const override {
std::cout << "Derived class" << std::endl;
}
};
Corrected version:
class Base {
public:
virtual void show() {
std::cout << "Base class" << std::endl;
}
};
class Derived : public Base {
public:
// Now correctly overrides Base::show()
void show() override {
std::cout << "Derived class" << std::endl;
}
};
int main() {
Derived d;
Base* b = &d;
b->show(); // Now calls Derived::show()
return 0;
}
Output:
Derived class
Key Rules for override:
- It must be used with a function that overrides a virtual function from a base class
- The function signature and return type must exactly match the one in the base class
- The base class function must be declared as
virtual overrideappears at the end of the function declaration, after anyconstand reference qualifiers
The Final Specifier
The final specifier has two uses in C++:
- Preventing a virtual method from being overridden in derived classes
- Preventing a class from being used as a base class
Making Methods Final
When applied to a virtual method, final prevents any derived class from overriding that method:
class Base {
public:
virtual void method1() {
std::cout << "Base::method1()" << std::endl;
}
virtual void method2() final {
std::cout << "Base::method2()" << std::endl;
}
};
class Derived : public Base {
public:
void method1() override {
std::cout << "Derived::method1()" << std::endl;
}
// Error: cannot override final method
// void method2() override {
// std::cout << "Derived::method2()" << std::endl;
// }
};
int main() {
Derived d;
Base* b = &d;
b->method1(); // Calls Derived::method1()
b->method2(); // Calls Base::method2() (can't be overridden)
return 0;
}
Output:
Derived::method1()
Base::method2()
Making Classes Final
When applied to a class, final prevents any other class from inheriting from it:
class BaseClass final {
public:
void someMethod() {
std::cout << "BaseClass::someMethod()" << std::endl;
}
};
// Error: cannot derive from final class
// class DerivedClass : public BaseClass {
// public:
// void anotherMethod() {
// std::cout << "DerivedClass::anotherMethod()" << std::endl;
// }
// };
Practical Examples
Example 1: Building a Robust Shape Hierarchy
#include <iostream>
#include <vector>
#include <memory>
class Shape {
public:
virtual ~Shape() = default;
virtual double area() const = 0;
virtual void draw() const {
std::cout << "Drawing a shape" << std::endl;
}
};
class Circle final : public Shape {
private:
double radius;
public:
Circle(double r) : radius(r) {}
double area() const override {
return 3.14159 * radius * radius;
}
void draw() const override {
std::cout << "Drawing a circle with radius " << radius << std::endl;
}
};
class Rectangle : public Shape {
private:
double width;
double height;
public:
Rectangle(double w, double h) : width(w), height(h) {}
double area() const override {
return width * height;
}
void draw() const override final {
std::cout << "Drawing a rectangle " << width << "x" << height << std::endl;
}
};
class Square final : public Rectangle {
public:
Square(double side) : Rectangle(side, side) {}
// Can't override draw() because it's final in Rectangle
// Can still override area() if needed (though not necessary here)
double area() const override {
return Rectangle::area(); // Just calls the base implementation
}
};
int main() {
std::vector<std::unique_ptr<Shape>> shapes;
shapes.push_back(std::make_unique<Circle>(5.0));
shapes.push_back(std::make_unique<Rectangle>(4.0, 3.0));
shapes.push_back(std::make_unique<Square>(2.0));
for (const auto& shape : shapes) {
shape->draw();
std::cout << "Area: " << shape->area() << std::endl;
std::cout << "------------------------" << std::endl;
}
return 0;
}
Output:
Drawing a circle with radius 5
Area: 78.5398
------------------------
Drawing a rectangle 4x3
Area: 12
------------------------
Drawing a rectangle 2x2
Area: 4
------------------------
Example 2: Interface Implementation with Override
#include <iostream>
#include <string>
#include <vector>
#include <memory>
// Logger interface
class Logger {
public:
virtual ~Logger() = default;
virtual void log(const std::string& message) = 0;
virtual void setLevel(int level) = 0;
virtual int getLevel() const = 0;
};
// ConsoleLogger implementation
class ConsoleLogger : public Logger {
private:
int logLevel;
public:
ConsoleLogger() : logLevel(0) {}
void log(const std::string& message) override {
std::cout << "[Console] " << message << std::endl;
}
void setLevel(int level) override {
logLevel = level;
}
int getLevel() const override {
return logLevel;
}
};
// FileLogger implementation
class FileLogger final : public Logger {
private:
int logLevel;
std::string filename;
public:
FileLogger(const std::string& file) : logLevel(0), filename(file) {}
void log(const std::string& message) override {
std::cout << "[File: " << filename << "] " << message << std::endl;
// In a real implementation, we would write to a file here
}
void setLevel(int level) override {
logLevel = level;
}
int getLevel() const override {
return logLevel;
}
};
int main() {
std::vector<std::unique_ptr<Logger>> loggers;
loggers.push_back(std::make_unique<ConsoleLogger>());
loggers.push_back(std::make_unique<FileLogger>("app.log"));
for (const auto& logger : loggers) {
logger->setLevel(1);
logger->log("Application started");
}
return 0;
}
Output:
[Console] Application started
[File: app.log] Application started
Best Practices
-
Always use
overridewhen overriding virtual functions:- It makes your code more readable
- It prevents subtle bugs
- It documents your intention clearly
-
Use
finalwhen:- You need to prevent further derivation for security/design reasons
- You're working on performance-critical code (the compiler can optimize non-virtual calls)
- You're building a class that isn't designed to be inherited from
-
Consider the inheritance hierarchy carefully:
- Don't mark methods as
finalunless you have a good reason - Classes marked as
finalcan't be mocked in some testing frameworks
- Don't mark methods as
How the Compiler Uses These Specifiers
Summary
- The
overridespecifier ensures that a method is actually overriding a base class method - The
finalspecifier prevents further overriding of methods or inheritance of classes - Both specifiers help catch errors at compile time rather than at runtime
- Using these specifiers makes code more robust and self-documenting
- These features are part of modern C++ best practices for inheritance hierarchies
Exercises
- Convert an existing class hierarchy in your codebase to use
overrideand see if you catch any bugs - Create a simple plugin system where some methods can be overridden but others are marked as
final - Design a class hierarchy for a game with different character types, using
overrideandfinalappropriately
Additional Resources
💡 Found a typo or mistake? Click "Edit this page" to suggest a correction. Your feedback is greatly appreciated!