19

While designing my first 'serious' C++ library, I'm asking myself:

Is it good style to derive ones exceptions from std::exception and it's offsprings?!

Even after reading

I'm still not sure. Because, besides common (but maybe not good) practice, I would assume, as a library user, that a library function would throw std::exceptions only when standard library functions failed in the library implementation, and it can't do anything about it. But still, when writing application code, for me it's very convenient, and also IMHO good looking to just throw a std::runtime_error. Also my users also can rely on the defined minimum interface, like what() or codes.

And for example, my user supplies faulty arguments, what would be more convenient, than to throw a std::invalid_argument, wouldn't it? So combined with the yet common use of std::exception I see in others code: Why not go even further and derive from your custom exception class (e.g. lib_foo_exception) and also from std::exception.

Thoughts?

Superlokkus
  • 381
  • 1
  • 3
  • 11
  • I'm not sure I follow. Just because you inherit from `std::exception` does not mean you _throw_ a `std::exception`. Also, `std::runtime_error` does inherit from `std::exception` in the first place, and the `what()` method comes from `std::exception`, not `std::runtime_error`. And you should definitely create your own exception classes instead of throwing generic exceptions such as `std::runtime_error`. – Vincent Savard Dec 22 '15 at 13:32
  • 3
    The difference is, that when my `lib_foo_exception` class derives from `std::exception`, the library user would catch `lib_foo_exception` by just catching `std::exception`, in addition to when he catches only the libary one. So I could also ask **Should my library exception root class inherit from std::exception** . – Superlokkus Dec 22 '15 at 13:40
  • @Superlokkus: What do you mean "in addition to"? You can only catch one exception at a time. – Lightness Races in Orbit Dec 22 '15 at 14:03
  • 3
    @LightnessRacesinOrbit I mean "...in addition to", like "How many ways are there to catch `lib_foo_exception`?" With inheriting from `std::exception` you can do it by `catch(std::exception)` OR by `catch(lib_foo_exception)`. Without deriving from `std::exception`, you would catch it *if and only if*, by `catch(lib_foo_exception)`. – Superlokkus Dec 22 '15 at 14:17
  • @Superlokkus: But surely that's _not_ what you want? You either want to catch all exceptions, or you want to catch a specific exception. `std::exception` should mean "all exceptions", not "all exceptions except `lib_foo_exception`. I guess you're reading `std::exception` as "only exceptions from the standard library"? – Lightness Races in Orbit Dec 22 '15 at 14:24
  • @LightnessRacesinOrbit You're right, I thought "all exceptions" should be equivalent to `catch(...)` not `catch(std::exception)`, (Technically it is still the first one, but from a reasonable semantic perspective) – Superlokkus Dec 22 '15 at 14:39
  • 2
    @Superlokkus: We sort of ignore `catch(...)`. It's there because the language allows for the case you're considering (and for "misbehaving" libraries), but that's not modern best practice. – Lightness Races in Orbit Dec 22 '15 at 14:45
  • 1
    A lot of the design of exception-handling in C++ tends to encourage coarser, more general `catch` sites, and likewise coarser transactions that model a user-end operation. If you compare it to languages which don't promote the idea of generalized catching of `std::exception&`, e.g., they often have a lot more code with intermediary `try/catch` blocks concerned with very specific errors, which somewhat diminishes the generality of exception-handling as it's starting to place a much stronger emphasis on manual error handling, and also on all the disparate errors that could possibly occur. –  Dec 22 '15 at 18:03

2 Answers2

33

All exceptions should inherit from std::exception.

Suppose, for example, I need to call ComplexOperationThatCouldFailABunchOfWays(), and I want to handle any exceptions that it could throw. If everything inherits from std::exception, this is easy. I only need a single catch block, and I have a standard interface (what()) for getting details.

try {
    ComplexOperationThatCouldFailABunchOfWays();
} catch (std::exception& e) {
    cerr << e.what() << endl;
}

If exceptions do NOT inherit from std::exception, this gets much uglier:

try {
    ComplexOperationThatCouldFailABunchOfWays();
} catch (std::exception& e) {
    cerr << e.what() << endl;
} catch (Exception& e) {
    cerr << e.Message << endl;
} catch (framework_exception& e) {
    cerr << e.Details() << endl;
}

Regarding whether to throw runtime_error or invalid_argument versus creating your own std::exception subclasses to throw: My rule of thumb is to introduce a new subclass whenever I need to handle a particular type of error differently than other errors (i.e., whenever I need a separate catch block).

  • If I introduce a new exception subclass for every conceivable type of error, even if I don't need to handle them separately, then that adds a lot of class proliferation.
  • If I reuse existing subclasses to mean something specific (i.e., if a runtime_error thrown here means something different than a generic runtime error), then I run the risk of conflicting with other uses of the existing subclass.
  • If I don't need to handle an error specifically, and if the error that I'm throwing exactly matches one of the existing standard library's errors (such as invalid_argument), then I reuse the existing class. I just don't see much benefit to adding a new class in this case. (The C++ Core Guidelines disagree with me here - they recommend always using your own classes.)

The C++ Core Guidelines have further discussion and examples.

Josh Kelley
  • 10,991
  • 7
  • 38
  • 50
  • This rationale doesn't make sense to me. Isn't this what `catch (...)` (with the literal ellipsis) is for? – Maxpm Jun 11 '18 at 22:17
  • 2
    @Maxpm `catch (...)` is only useful if you don't need to do _anything_ with what's thrown. If you want to do something - e.g., show or log the specific error message, as in my example - then you need to know what it is. – Josh Kelley Jun 12 '18 at 12:42
10

I would assume, as a library user, that a library function would throw std::exceptions only when standard library functions failed in the library implementation, and it can't do anything about it

That is an incorrect assumption.

The standard exception types are provided for "commoner" use. They are not designed to only be used by the standard library.

Yes, make everything ultimately inherit from std::exception. Oftentimes, that'll involve inheriting from std::runtime_error or std::logic_error. Whatever is appropriate for the class of exception you're implementing.

This is of course subjective — several popular libraries completely ignore the standard exception types, presumably to decouple the libraries from the standard library. Personally I think this is extremely selfish! It makes catching exceptions that much more difficult to get right.

Speaking personally, I often just throw a std::runtime_error and be done with it. But that's getting into a discussion about how granular to make your exception classes, which is not what you're asking.

Lightness Races in Orbit
  • 8,755
  • 3
  • 41
  • 45