Why Inherit from std::exception in C++
In C++, developers have the freedom to throw almost any type, from
primitive integers to dedicated structs. However, adhering to the
standard convention of inheriting custom exception classes from
std::exception or its child classes like
std::runtime_error is vital for writing robust,
maintainable, and predictable software. This article outlines the
architectural, interoperability, and practical benefits of deriving your
custom exceptions from the standard hierarchy.
1. Consistent Interface via
what()
The base class std::exception defines the virtual
method:
virtual const char* what() const noexcept;By inheriting from std::exception (or using
std::runtime_error), your custom exception guarantees this
interface. This allows any logging framework, debugging utility, or
generic error handler to retrieve a human-readable explanation of the
failure without needing to know the exact derived type at compile
time.
2. Polymorphic Handling and Unified Catch Blocks
Standard library functions throw exceptions that derive from
std::exception (such as std::out_of_range or
std::bad_alloc). When your custom types also inherit from
this hierarchy, call sites can catch and handle both standard and custom
failures uniformly:
try {
performDatabaseOperation();
} catch (const std::exception& e) {
std::cerr << "Operation failed: " << e.what() << '\n';
}Catching by const std::exception& acts as a
high-level safety net at architectural boundaries—such as thread roots
or main()—ensuring that error descriptions are
preserved.
3. Avoiding the Blindness of
catch (...)
While catch (...) catches everything, it strips away all
type and context information. When non-standard types are thrown (like
raw strings, integers, or unrelated classes), the developer is forced to
rely on catch (...) without access to an error message or
backtrace information. Deriving from std::exception ensures
errors can be intercepted safely with informative diagnostics.
4. Third-Party and Ecosystem Interoperability
Most modern C++ frameworks, test suites, and third-party libraries
assume that thrown exceptions derive from std::exception.
For example:
- Testing frameworks (such as Google Test or Catch2) automatically
print
e.what()when an unexpectedstd::exceptionis caught during a test. - Web frameworks, GUI event loops, and game engines typically route unhandled standard exceptions to user-facing crash dialogs or telemetry services.
Throwing an isolated, non-conforming type often leads directly to
std::terminate() because intermediate layers do not
recognize it.
5.
std::runtime_error Reduces Boilerplate
While std::exception requires you to manage and
implement the what() method yourself,
std::runtime_error provides a built-in constructor
accepting a string message and manages its memory safely:
#include <stdexcept>
#include <string>
class DatabaseConnectionError : public std::runtime_error {
public:
explicit DatabaseConnectionError(const std::string& msg)
: std::runtime_error("Database failure: " + msg) {}
};Choosing std::runtime_error simplifies custom exception
implementation while maintaining full compatibility with standard
exception handling semantics.