Don’t Try Too Hard! – Exception Handling

Among C++ developers there often appears to be a misconception about what it means to deal with code that can throw exceptions. The misconception is that the possibility of exceptions means one has to `try` and `catch` often and almost everywhere. I will try to explain why I think that is wrong and where I think `try`/`catch` is appropriate and where not.

Exception type multitude

One of the biggest problems with Exceptions is that you sometimes don’t know for sure which kind of errors might occur during the execution of a piece of code and what exceptions it therefore might throw. Since the possible errors are an implementation detail you should not need to care. The types of exceptions that can occur therefore should be restricted to a certain set of exception base classes and their derivates. If a developer does not know what that set is or if it is poorly designed or documented, she might be compelled to write a ton of catch handlers including a catchall in case she missed a type:

void foo() {
  try {
    doSomething();
    doSomethingElse();
  }
  catch (MyException& me) {
    //handle this one
  }
  catch (SomeOtherException& soe) {
    //handle this one, too
  }
  catch (SomeExceptionIAmNotSureOf& e {
    //better be safe than sorry...
  }
  catch (...) {
    //you know Justin? Just in Case?
  }
}

Ugh. The two simple lines of logic are almost invisible between a ton of error handling. And for sure, the catch handlers will do almost the same and you have a nice example of code duplication. Therefore the set of exception base classes used in a library should be as small as possible, preferably one. Two is acceptable, too, e.g. if you use a lot of standard library features that can throw exceptions but don’t want to derive your own exception classes from `std::exception`.

If you have only one common exception base class, you can limit the number of catch handlers to that class plus maybe one or two special classes that you want to handle in a special way. (Just make sure you catch the derived exceptions before the exception base class.)

Provide a well defined set of exception classes that may be used in your code. Any exception thrown in your code should be of or derive from one of those classes.

Catching but not handling exceptions

Having restricted the number of catch handlers per try let’s have a look at the catch handlers that don’t really react to the error but do something else.

Cleaning up

One of the best known, overused and in my opinion completely mislead example for exception handling is cleaning up stuff. It ranges from freeing allocated memory over closing files, database transactions, to resetting values that have temporarily been changed. In general, the pattern is beginning some action, executing the possibly failing operation, and then either finishing the action or reversing it, depending on if an exception has been thrown or not:

void foo() {
  startSomething();

  try {
    thisMightThrow();
  }
  catch(/* whatever might be thrown */) {
    reverseSomething();
    throw;
  }
  
  commitSomething();
}

The committing part may be trivial or completely missing, but that is not the point. The point is that C++ has a very important language feature that is meant and perfectly able to handle any cleanup that has to be done: Destructors. You might say that destructors are only meant to do the cleanup after an object of the class has done its purpose, but you should think of it the other way round: If you have something to be cleaned up, make it a class with the proper destructor.

There are many classes whose sole purpose is providing a destructor that does the cleanup. Best known of these are smart pointers and scoped locks. The pattern is commonly known as “RAII”, meaning “Resource Acquisition Is Initialization” and coined by Bjarne Stroustrup himself. However, it is not reserved for resources like memory, files and locks, it can be applied to anything that has to be cleaned up. Having to clean up something means to have a responsibility, therefore Jon Kalb modified the meaning of RAII in a brilliant talk: “Responsibility Acquisition Is Initialization”. Make sure to watch all three parts of that talk, it is worth every second.

With RAII, the above code could look like this:

struct SomethingRAII {
  bool _needCleanup;
  SomethingRAII() : _needCleanup(true) {
    startSomething(); 
  }
  ~SomethingRAII() {
    if (_needCleanup) reverseSomething();
  }
  void commit() {
    commitSomething();
    _needCleanup = false;
  }
};

void foo() {
  SomethingRAII mySomething;
  thisMightThrow();
  mySomething.commit();
}

That’s it. The whole function has been shrunk to three lines, because you don’t have to care about the “what if there is an exception” case any more. It has several advantages:

  1. It’s easier to read, because usually you only want to know about the normal non-exceptional things that happen. If a reader is interested in the cleanup and the RAII class has a good name he will know that he has to look into the destructor.
  2. It does the cleanup always, for everything that might be thrown. You can’t forget to catch any newly introduced exceptions or something you did not even know you could catch, e.g. access violations (some compilers throw them like ordinary exceptions).
  3. It follows the Separation of Concerns and Single Responsibility Principles: The function contains only the normal logic that has to be executed and not a mixture of normal logic and cleanup for error cases.

Use RAII classes instead of try/catch  for cleanup in the presence of exceptions.

Adding information to the exception

This is not as clear-cut as cleanup. I often see catch handlers that just add some plain text to an exception’s `what()` message and rethrow it:

void bar() {
  try {
    doSomething();
  }
  catch (MyException& me) {
    throw MyException("Exception in void bar(): " + me.what());
  }
}

This is pure debugging information and adds no value to the program, because you normally can’t act on it at the location where the exception is really handled except when you are in a debugging session. Instead, it buries the interesting action inside the try block, making the function less readable. If you need such information once to debug a tricky problem, then by all means add the try/catch, but don’t check it in, because you hopefully won’t ever need it again.

However, sometimes you might want to add information that is needed to handle the exception correctly, e.g. to call the same function with different parameters. In my experience that is at most very rarely the case, but it would be an example where adding information is acceptable.

Don’t misuse exceptions for debugging purposes.

Changing the exception type

There are two kinds of situations where I have seen catch handlers that change the exception type. One is at the boundary of (third party) libraries, the other is at arbitrary locations in someone’s code, often together with the “adding information” example above and including “catchalls”, i.e. `catch(…)` to change anything that might come flying into something that is considered more meaningful.

Type change inside a library

Changing an exception’s type inside a library (i.e. not at library boundaries) usually either means the original exception did not have the correct type or it can not be caught at the handler because the exception type hierarchy is messed up in one way or the other. As written in the first paragraph, if exceptions are the preferred error handling mechanism in a library, the library should define a set of exception base classes and any exception thrown inside the library should derive from those.

Another reason for changing an exception’s type inside a library may be to add information, but since the catch site will usually not know exactly what the original type was, it also destroys some information. In addition, as written above, adding information is often only used for debugging reasons and should be avoided in production code.

Similar to changing the type of an exception is a complete change of the error handling method, e.g. switching between exceptions and return codes or state variables. For the sake of consistency and maintainability, you should stick to one method of error reporting throughout your library.

Be consistent in your use of exceptions.

Type change at library borders

In contrast to type changes inside a library, I consider type changes at library borders a good practice. The reasons are encapsulation and consistency. If you use a third party library, you will either want to restrict that use to a small set of wrapper classes, or you will use it throughout your code except for the interfaces of your library. After all, you don’t want clients of your library to be dependent on a third party library that is only an implementation detail. Therefore you have to catch and translate exceptions thrown by the third party library either in your wrapper classes or hidden in your interface classes.

A special case of library borders are callbacks and event handlers you provide to other libraries. The manufacturer of those libraries, e.g. a GUI framework, can not know what kind of exceptions you use, and they can not prepare for them except using a catchall and ignoring all information the exception might provide. If not explicitly documented otherwise, assume that other libraries expect your callback and handler functions to be `noexcept`. Some libraries may provide an exception base type they can handle, so you could change your exception into a derived type of theirs, but if possible you should prefer to handle the error instead of letting it progress into unknown territory.

Exceptions are part of a library’s interface but can be leaked more easily. Encapsulate them properly.

Handling exceptions

Actually handling exceptions is usually not very hard, since you usually know what you have to do if an exception occurs. Among the usual reactions to an exception are:

  1. Logging the error, e.g. writing it into a log file and/or notifying the user with a message box or something similar.
  2. Ignoring the error, because the tried action was not vital to the function.
  3. Trying to get the desired behavior by doing something else.

The hard part is to determine where in the code you can actually do something about them. I have seen code where a series of actions is performed one after the other. If one action did not succeed, the other actions could not be performed properly, so the author did introduce a state variable that was set to fail if an action did not succeed and each action had an early return if that variable was set to fail. In addition, each action had a try/catch block where the catch handlers set the state variable:

class X {
  bool mbOK;
public:
  void foo() {
    mbOk=true;
    action1();
    action2();
    action3();
    //...
  }
private:
  void action1() {
    if (!mbOK) return;
    try {
      doSomething();
    }
    catch (SomeException& se) {
      mbOk = false;
    }
  }
  //etc...
};

It was a vast multiplication of the same logic throughout all of those functions. The reason was simply that the error handling had been done too often, too early, too deep in the call hierarchy. It should have been done one level up, in `foo()`. If you look closely, the functions did not really react on the error. Instead, they just set a flag for someone else to react on. This is not error handling, it is error translation, from exceptions to a state variable. That is often the case when exception handling occurs at the wrong place: No real action is taken, because it is too early to completely handle the error.

Don’t catch exceptions too early. Only catch them if you can really do something about the error.

Conclusion

In my experience, there are only two, maybe three occasions in which you should use `try`/`catch`: Error translation at library borders and complete error handling at the place where you have all the information needed to act properly. The third case would be adding information that is needed at the catch site, but I have never seen that one in production code.

There might be other cases I have not come across yet, so I would be happy if you have examples.

Previous Post
Next Post

17 Comments


  1. Conclusion

    The fourth case would be adding information for the user. But noone thinks about proper error messages.

    Reply

    1. Adding information can be a valid reason for catching an exception. However, in most cases where I have seen information added to an exception it was not helpful for the user but should have been written to a log file for a maintainer instead.

      Usually if an action fails due to an exception you can only catch it at the appropiate level and give the user a message “there has been an error performing that action (and you can do nothing about it)”.

      Exceptions caught early and rethrown with added information more often than not turn out to lead to messages like “there has been an error performing that action because something went wrong in function foo, file X, line Y (and you still can’t do anything about it)”

      In many, but not all cases, when you can do something about it, e.g. provide correct input, that should be checked before the throwing function is called. A usual exception that can not be checked beforehand is a network error or database connection loss. Those would be the rare cases where you could indeed add some information to let the user check the network etc.

      Reply

      1. Those would be the rare cases

        I differ errors according to recoverability and include a Retry button in the error dialog. Many many many errors are recoverable (including removing floppy disc while writing; tests with Windows NT 4).
        Exceptions are a superb mechanism to transport and extend all info needed by user or support. “But noone thinks about proper error messages.” == no transport is needed == cheapest try throw catch used == no benefit for user and support == ultimately replaceable by std::abort(). But if you do it right, the user can skip the usually necessary search for the source of the error.

        Reply


  2. I’m wonderig if it’s a good idea to derive a RAII class from, e.g., boost::noncopyable.

    Reards

    Reply

    1. Depends. There are RAII classes that handle copying (think of std::shared_ptr) or are move-only (std::unique_ptr). However, many are used only for single, temporary use (typically prefixed scoped) and neither copyable nor movable. I’d prefer to explicitly delete unsupported operations, since afaik there is no boost::noncopyable_nonmovable 😉

      Reply

  3. Another point you mention I find interesting is:

    “(…) use a lot of standard library features that can throw exceptions but don’t want to derive your own exception classes from std::exception.”

    I would question the design. Why (serious question) would anyone not derive from std::exception? It doesn’t make any sense to me.

    All examples I know that do not derive from std::exception are historical accidents.

    Are there any real live C++ code bases where not deriving std::exception has been a Good Thing?

    cheers,
    Martin

    Reply

    1. While I agree that such a design would be questionable most times, there could be cornecases. Consider writing a sublibrary that for a bigger library you can’t change, whose main exception does not derive from std::exception, you then might want to derive MySubLibBaseException from that main exception instead of std::exception. I would prefer having std::exception as single base class, too, although my coding style would explicitly mention a single user defined base exception for all user defined exceptions to facilitate the distinction in catch handlers.

      Reply

      1. Distinction in the catch handler is mostly a fallacy too, IMHO.

        We must distinguish (write multiple catch clauses) today, because we have different (base) exception classes.

        We then do the same thing (log + rollback) in each handler.

        There might be some cases where we consider the caught “error” to be something else than log+rollback. (Abstractly speaking.) It is highly unlikely that what we consider “something else” is divided along the exception class hierarchy, so we end up with if-s and switches in addition to the multiple catch clauses.

        Reply

        1. I could imagine a distinction in the catch handler if my exception base class provides more info than just what(), e.g. for different levels of detail in the message box and log file.

          Reply

      2. In the embedded world, deriving from std::exception may a ‘no no’ as this pulls in std::string, which uses dynamic allocation for its storage. This may be ‘banned’ as many embedded projects will avoid dynamic memory allocations completely. My own STL-like embedded library defines its own exception base class for this very reason.

        Reply

        1. I am somewhat surprised to hear that you have exceptions at all in the embedded library. I can’t remember any embedded developer I have talked to not mentioning that besides dynamic memory allocations, exceptions are the second forbidden feature in embedded C++.
          Of course, if you don’t use the parts of the standard library that throw std::exception, there is no need to derive your own classes from it.

          Reply

  4. One of these days I should write my own article about what I think is wrong with exception as implemented today in so many ways.

    Anyways, I’d like to call out one issue here:

    You seem to be opposed to “adding debugging information” (I’d call it context) to the exceptions, as you seem to think that a proper catch site should handle the exception programatically(?), and that “adding information (…) used for debugging (…) should be avoided in production code”.

    To which I can only say: WTF? (In a friendly way :-))

    I think mostly the only sane thing catch-code can do, when you otherwise use proper RAII techniques as you so aptly describe, when a proper, high level catch site catches a base class (and really it should only be catching base classes, right?) is log the full exception information and abort whatever it had been trying.

    The crucial bit here is log the full information: The more information the better. It might be buried in a log file, it might be totally incomprehensible to the average user, but eventually someone will look at the logs and that someone will be well served by every bit of so called “debugging info” you can offer.

    That’s the reason Java and C# exception with their built-in call stacks are so much more useful that what we have in C++, even though they have the same problems otherwise. (Note: I do not propose call stacks for C++ exceptions.)

    That’s the reason I cringe when I see the what() message VS offers for std::out_of_range: It’s “Index out of range.” – WTF? How about “Index 7 out of range (0 – 6) in vector object located at address 0x12345678.” – would that be too hard to implement? Sure it’s debugging info – and sure I want (some) debugging info in my production code!

    cheers,
    Martin

    Reply

    1. Hi Martin, thanks for your thoughts.

      I want to stress that I am not against adding context in general, but against adding information that is only used for debugging, i.e. that can not be seen during normal execution.

      Adding information needed for the correct handling of the error is ok. If handling the error means logging a stack trace (or parts of it) so that support can guess what might be the source of the problem, then adding information about the stack trace is perfectly ok, because that stack trace is needed a the handler site.

      On the other hand, if handling the exception means to ignore it or to just pop up an “Unkown error” message, without logging the what() somewhere, then adding information to that message is useless, unless you are in a debugging session.

      What I have seen in code is that exception modifiers that have been introduced “temporarily” to debug some error remain years after the error is not even possible any more.

      Reply

      1. Thanks for the clarifying thoughts.

        Aside: “Unknown Error” messages should be a punishable offense 🙂

        “Anwendung konnte nicht gestartet werden, weil die Anwendungskonfiguration nicht korrekt ist.” – anyone?

        “Unknown error” messages (like the one above) are like telling you: “Oh I known some what went wrong, but I won’t tell you anything.” 🙂

        Reply

  5. Arne, thanks for this post. Both newbies and experienced users will benefit from it.

    You’re point about handling versus translating errors is very good.

    I think there is a role for exceptions in trouble shooting, but not a big one. For that I recommend Boost.Exception which is a safe and sane way to add information to a thrown exception.

    Reply

    1. Hi Jon, thanks for taking the time to read this. Glad you appreciate it.

      Reply

Leave a Reply

Your email address will not be published. Required fields are marked *