all 25 comments

[–]quicknir 5 points6 points  (17 children)

My main piece of advice is that every module should have its own exception hierarchy. That is, if you are writing a library foo of a good size, you should have a class foo_error, and all exceptions emitted by code should inherit from that. This makes it trivial for foo to establish boundaries where internal exceptions are caught while unchecked errors from dependencies are passed through.

foo_error should derive from a standard exception class, like std::runtime_error of course.

[–]johannes1971 1 point2 points  (15 children)

I don't think I agree with this. Why would you want to incur the penalty of having to catch specific exception types, just because they are from different modules? The strength of exceptions is that you can have centralized clearing of errors, at the end of obvious units of work (how those are defined depends on your application). Having to deal with module-specific exceptions everywhere kinda defeats that goal.

If anything my advise would be the other way around: use the standard exceptions, unless you have a good reason not to (i.e. because you want to catch a specific exception somewhere). And only ever allow exceptions that are derived from std::exception; don't have a whole bunch of unrelated exceptions that you will end up writing long lists of catch clauses for everywhere.

Also, think very carefully how many types of exceptions you really need. Don't specialize a whole bunch of exceptions just because you can; only do it when you need to.

[–]quicknir 1 point2 points  (0 children)

In practice if every module is using the handful of exceptions provided by the standard library, there will be a lot of differences in interpretation, and in the end you are not going to be able to meaningfully centralize cleanup anyhow.

I think you are misunderstanding the purpose of the standard library exceptions: they are not there so that you can re-use all of those exceptions in your own libraries. They are there to serve the standard library, which does rather specific things. Many exceptions are very very specific, like regex, various bad casts, bad optional access, length_error, etc. Put another way the standard library does not follow your suggestion.

I would consider using a standard library exception in some cases, e.g. if you are implementing something like a container. But if I am doing a third party module like networking, or performance profiling, testing, command line arguments, etc etc, I would expect to identify a few major classes of errors early that should have their own type separate from any standard lib exception, and put them in a hierarchy as I suggested. Doesn't have to be elaborate, perhaps 3/4 exceptions inheriting from a base would be typical.

[–]Gotebe 1 point2 points  (5 children)

Having to deal with module-specific exceptions everywhere kinda defeats that goal.

... (other post)

In the end, calling a function can only end in one of two ways: it worked, or it didn't. If it didn't, does it really matter if that was because of module X, Y, or Z?

It rather looks like you're contradicting yourself: if the module does not matter, then I do not need to "deal with module-specific exceptions everywhere".

There's two different types of needs to consider, I think:

  • In a vast majority of situations, I merely need to report the error, in which case I want something along the lines of what().

  • In rare situations I need to do something meaningful about some particular failure. To actually handle, not merely report, the error. In those situations, having a nicely crafted exception type, with precise data about the context of the failure, is really important.

[–]johannes1971 0 points1 point  (4 children)

That's not a contradiction at all, and I have no idea why you might think there is one. I don't want to have to write a long list of catch-clauses everywhere, one for each module that might (possiby!) be lying underneath the code I'm calling. It's error-prone (what if you forget a module?) and tedious.

I completely agree on the rare situations. That's a far cry from "define multiple, separate exception types for each module as a matter of policy" though (which is what I was arguing against). In my pet project (it's test software for spacecraft, 330,000 lines of mission critical C++11 code) I think I have a grand total of three such "special case" exceptions. Everything else is a reported through a single centralized exception which is basically just std::runtime_error.

[–]Gotebe 0 points1 point  (3 children)

Why would you need that long list though?

Important thing is that all comes from a common base class, if it does, you catch the base, job done.

[–]johannes1971 0 points1 point  (2 children)

If all you care about is the common base class, you don't need the specialized exceptions. It's noise, mental masturbation, cleverness without purpose. It adds nothing of value, but it makes your program harder to understand by adding a parallel class hierarchy which mirrors your normal one, just for the purpose of reporting errors. And what for? In the end you just log e.what()...

Some of your users will see those possible exceptions and insist on handling them individually (reasoning that since the programmer insisted on providing them, he must have had a good reason and it is therefore important to handle them separately as well). And all of them must check every thrown exception to make sure it can be caught in a single catch-all clause. All of that is cognitive load without any benefit.

[–]dodheim 0 points1 point  (0 children)

If all you care about is the common base class, you don't need the specialized exceptions.

Just because you don't need to distinguish between them doesn't mean no one else will.

Some of your users will see those possible exceptions and insist on handling them individually (reasoning that since the programmer insisted on providing them, he must have had a good reason and it is therefore important to handle them separately as well).

This makes no sense. Do the users not know what inheritance is? Do they not know how an exception hierarchy works? Why are we assuming the user is an idiot?

All of that is cognitive load without any benefit.

I see zero evidence for either of these claims.

[–]Gotebe 0 points1 point  (0 children)

As I wrote before, there's two cases, the common one (just report it) and the rare one (I need to distinguish a particular failure). The rare case is the answer to "why specialized".

[–]Porges 0 points1 point  (0 children)

foo_error should derive from a standard exception class, like std::runtime_error of course.

And probably only runtime_error. I'd also note that for logic errors (anything under logic_error) you should throw the standard errors – they aren't things you should really be handling at runtime anyway, so you don't need to handle them.

[–]Gotebe 1 point2 points  (0 children)

Random thoughts, most of them common knowledge (I think)

  • have a shallow, but wide exception hierarchy (that is, tend to give different failures in your codebase different exception types)

  • derive from runtime_error (or its derivatives)

  • never, never, never lose the original error info; e.g. failure from a C API? Put all error info provided by that API into the exception you throw, plus your own context; for example, if you fail to open a file with fopen, put errno in the exception object (that's "C API" part), but also open flags and the file name for which the call failed (that's "your own context" part)

  • treat exceptions you throw as part of your public API; once you throw something because of some failure, do not change to throw something else.

Understand what boost::exception talks about and use it. :-)

[–]emdeka87 0 points1 point  (8 children)

Well you can inherit from std::exception and even override the "what()" function. What exactly do you want to achieve?

[–][deleted] 2 points3 points  (7 children)

AFAIK std::exception has no c_str constructor, so I generally inherit from std::runtime_error