C++ FAQ Celebrating Twenty-One Years of the C++ FAQ!!!
(Click here for a personal note from Marshall Cline.)
Section 17:
[17.6] Exception handling seems to make my life more difficult; clearly I'm not the problem, am I??

Absolutely you might be the problem!

The C++ exception handling mechanism can be powerful and useful, but if you use it with the wrong mindset, the result can be a mess. If you're getting bad results, for instance, if your code seems unnecessarily convoluted or overly cluttered with try blocks, you might be suffering from a "wrong mindset." This FAQ gives you a list of some of those wrong mindsets.

Warning: do not be simplistic about these "wrong mindsets." They are guidelines and ways of thinking, not hard and fast rules. Sometimes you will do the exact opposite of what they recommend — do not write me about some situation that is an exception (no pun intended) to one or more of them — I guarantee that there are exceptions. That's not the point.

Here are some "wrong exception-handling mindsets" in no apparent order:

  • The return-codes mindset: This causes programmers to clutter their code with gobs of try blocks. Basically they think of a throw as a glorified return code, and a try/catch as a glorified "if the return code indicates an error" test, and they put one of these try blocks around just about every function that can throw.
  • The Java mindset: In Java, non-memory resources are reclaimed via explicit try/finally blocks. When this mindset is used in C++, it results in a large number of unnecessary try blocks, which, compared with RAII, clutters the code and makes the logic harder to follow. Essentially the code swaps back and forth between the "good path" and the "bad path" (the latter meaning the path taken during an exception). With RAII, the code is mostly optimistic — it's all the "good path," and the cleanup code is buried in destructors of the resource-owning objects. This also helps reduce the cost of code reviews and unit-testing, since these "resource-owning objects" can be validated in isolation (with explicit try/catch blocks, each copy must be unit-tested and inspected individually; they cannot be handled as a group).
  • Organizing the exception classes around the physical thrower rather than the logical reason for the throw: For example, in a banking app, suppose any of five subsystems might throw an exception when the customer has insufficient funds. The right approach is to throw an exception representing the reason for the throw, e.g., an "insufficient funds exception"; the wrong mindset is for each subsystem to throw a subsystem-specific exception. For example, the Foo subsystem might throw objects of class FooException, the Bar subsystem might throw objects of class BarException, etc. This often leads to extra try/catch blocks, e.g., to catch a FooException, repackage it into a BarException, then throw the latter. In general, exception classes should represent the problem, not the chunk of code that noticed the problem.
  • Using the bits / data within an exception object to differentiate different categories of errors: Suppose the Foo subsystem in our banking app throws exceptions for bad account numbers, for attempting to liquidate an illiquid asset, and for insufficient funds. When these three logically distinct kinds of errors are represented by the same exception class, the catchers need to say if to figure out what the problem really was. If your code wants to handle only bad account numbers, you need to catch the master exception class, then use if to determine whether it is one you really want to handle, and if not, to rethrow it. In general, the preferred approach is for the error condition's logical category to get encoded into the type of the exception object, not into the data of the exception object.
  • Designing exception classes on a subsystem by subsystem basis: In the bad old days, the specific meaning of any given return-code was local to a given function or API. Just because one function uses the return-code of 3 to mean "success," it was still perfectly acceptable for another function to use 3 to mean something entirely different, e.g., "failed due to out of memory." Consistency has always been preferred, but often that didn't happen because it didn't need to happen. People coming with that mentality often treat C++ exception-handling the same way: they assume exception classes can be localized to a subsystem. That causes no end of grief, e.g., lots of extra try blocks to catch then throw a repackaged variant of the same exception. In large systems, exception hierarchies must be designed with a system-wide mindset. Exception classes cross subsystem boundaries — they are part of the intellectual glue that holds the architecture together.
  • Use of raw (as opposed to smart) pointers: This is actually just a special case of non-RAII coding, but I'm calling it out because it is so common. The result of using raw pointers is, as above, lots of extra try/catch blocks whose only purpose in life is to delete an object then re-throw the exception.
  • Confusing logical errors with runtime situations: For example, suppose you have a function f(Foo* p) that must never be called with the NULL pointer. However you discover that somebody somewhere is sometimes passing a NULL pointer anyway. There are two possibilities: either they are passing NULL because they got bad data from an external user (for example, the user forgot to fill in a field and that ultimately resulted in a NULL pointer) or they just plain made a mistake in their own code. In the former case, you should throw an exception since it is a runtime situation (i.e., something you can't detect by a careful code-review; it is not a bug). In the latter case, you should definitely fix the bug in the caller's code. You can still add some code to write a message in the log-file if it ever happens again, and you can even throw an exception if it ever happens again, but you must not merely change the code within f(Foo* p); you must, must, MUST fix the code in the caller(s) of f(Foo* p).

There are other "wrong exception-handling mindsets," but hopefully those will help you out. And remember: don't take those as hard and fast rules. They are guidelines, and there are exceptions to each.