Throwing generic exceptions such as std::exception
, std::logic_error
and std::runtime_error
will have a
negative impact on any code trying to catch these exceptions.
From a consumer perspective, it is generally a best practice to only catch exceptions you intend to handle. Other exceptions should ideally not be
caught and let propagate up the stack trace so that they can be dealt with appropriately. When a generic exception is thrown, it forces consumers to
catch exceptions they do not intend to handle, which they then have to re-throw.
Besides, when working with a generic type of exception, the only way to distinguish between multiple exceptions is to check their message, which is
error-prone and difficult to maintain. Legitimate exceptions may be unintentionally silenced and errors may be hidden.
For instance, in the following code, the fact that checkState
throws a generic exception leads us to catch a permission error that
shouldn’t have been caught:
void openFile(File& file) {
if (!has_permissions(file)) {
throw std::invalid_argument("Couldn't open file");
}
// ...
}
void checkState(File const& file) {
if (!file.is_valid()) {
throw std::exception(); // Noncompliant
}
// ...
}
void test(File file) {
try {
openFile(file);
checkState(false);
closeFile(file);
} catch (std::exception& s) {
// If we don't have the correct permissions to open, the
// invalid_argument exception will be caught and we will try closing a
// file that was never opened
closeFile(file);
}
}
Therefore, throwing the most specific exception possible is recommended so consumers can handle it intentionally.