Using Exceptions in C++

C++ is big – it has been said that any given programmer only ever uses about 40% of the language’s features. The trouble is that it is a different 40% for each person. Exceptions are a great example of this, some people swear by them while many coding standards specifically ban/discourage them (cf: google, mozilla). It is ironic that a feature designed to make code safer is sometimes regarded as being too dangerous to use.

Personally I like exceptions, but even I realise that they have there limitations. This post is an attempt to formalise some guidelines about when exceptions should be used and when they should be avoided beyond the usual language rules. I should mention at this stage that most of my experience is in desktop client/server software. C++ is used in all sorts of places these days, and what works on desktops and beefy servers may not suit the embedded world (for example).

When to Catch

My rule of thumb is “Do not let exceptions escape from a function you didn’t explicitly call yourself“. This includes destructors, callbacks, thread functions, WNDPROCs, and any other miscellaneous way your functions can be entered (it does not include constructors or virtual functions – exceptions are very useful in those). In general, all these things should catch and handle all exceptions.

Your program will die if an exception escapes a thread. If you are lucky your runtime will do something clever and your program will die painlessly but possibly the OS will have to dispatch the process messily. Either way, your users will not be impressed, so you should always wrap thread functions in try{}catch blocks. In the best case you might be able to signal that an operation failed to the main thread, which can restart it if required. In the worst case you can at least log what happened before exiting.

Lots of third part libraries communicate with your code using callbacks that you supply. You should always ensure that any exceptions are caught before returning back into third party code, since you can never be sure if the library does the right thing. C style libraries like LibCURL are right out, they will probably leak handles and memory as the stack is unwound. C++ libraries may (or may not) be better but could do things you do not expect, like swallow the exceptions themselves instead of letting them fall through (boost::iostreams). Also, some libraries actually call you back on a different thread (boost::asio) so the advice in the previous paragraph also applies here.

You should always be prepared to catch any exceptions that are documented by any C++ libraries you use, especially things like boost::filesystem which can throw at any time.

What to Throw

My advice is to create a small hierarchy that is derived from std::runtime_exception unless you are already using a custom exception class. Don’t try to get clever and throw std::string or char*. Design your hierarchy around how the exceptions are to be handled, rather than what can go wrong. For instance, if there are 5 different ways your program throw exceptions, but only 3 different things that can happen in response then you only need 3 types of exceptions.

In my experience, exceptions fall into two categories: recoverable and fatal. Recoverable exceptions will be caught within a layer, or perhaps the next layer up which can then reattempt the operation (or perhaps just log and ignore the problem if the operation was not crucial.) Fatal errors are usually not caught until the outer loop of the program, where they are logged before the program can be shutdown cleanly. In general, the exceptions you expect to recover from should derive from exceptions you expect to be fatal.

I tend to name my exception types based on how they are handled and what circumstance they represent, like so:

class UnrecoverableFileReadException : public std::runtime_error
	// thrown when a file cannot be read correctly, ie: the file exists but is misformatted
	UnrecoverableFileException( const std::string& msg ):std::runtime_error(msg) {};
class FileReadException : public UnrecoverableFileReadException
	// thrown when a file cannot be read but the user can be prompted to select another file
	TemporaryFileException( const std::string& msg ):UnrecoverableFileReadException(msg) {};

When throwing, always construct the exception with a sensible message if only for logging and debugging purposes. Normally this message would not be shown to the user since it will be hard to localise. Add additional members to your exception class if you want to include other information with the exception. Just remember that exceptions must be copyable.

ostringstream oss;
oss << "Could not open file " << filename << " the error was: " << errno;
throw TemporaryFileException( oss.str() );

If you really want to go the whole hog consider using boost::exception which builds upon similar ideas.

When to Throw

Exceptions should never be thrown if everything is running perfectly. Only when something goes wrong should throwing an exception be considered an option. I have seen code that threw exceptions to signal the end of an enumeration or to signal that the results of a query were empty – I consider these examples to be a terrible use of the feature. Remember that an exception is not just an error, but something really outside of normal program flow.

I also tend not to use exceptions for logic errors. If an algorithm can fail then the calling code should be prepared to handle a return code signalling an error. Likewise I would not throw if a database query returned no results – an empty result set is inside the bounds of normal program flow. However, I would consider throwing an exception if the query failed due to the database being unavailable.

The best places to use exceptions are in situations where your program is using resources that are not under your control, including anything to do with IO. Both files and network connections exist outside of your program and can become unavailable at any point due to any number of reasons. In many cases the problems are transient and all your program needs to do is try again in a few minutes – a program that quits each time the DNS, directory server, or external database cannot be reached will not survive for very long in any production environment. Exceptions allow you to back out of an operation without too much trouble and handle the problem in a sensible location.

One problem you might encounter is that is often hard to retrofit exceptions into code that wasn’t designed for them. In this case, my advice about not letting exceptions fall through 3rd party code also applies to legacy code that you own. This is not to say that you cannot use exceptions at all, just that you may have to take steps to keep exception handling within the layers of your application.

5 thoughts on “Using Exceptions in C++

  1. Aaron

    They biggest problem I have with C++ exceptions (Issues of whether to use them at all aside) is that their handling is not mandated. This makes them almost completely useless in my experience unless you are the sole developer working on the code who knows the application inside and out.

    The reason I find unmandated exceptions to be such an issue is that documentation is appalling in a large contingent of source code libraries used in real-world projects. Therefore, a lot of the time you just don’t know what exceptions to catch. In order to work that out you have to trawl through the code looking for throws. The reality is that developers just don’t do this. It’s too much work when it shouldn’t be and they resort to either not catching them, or using catch(…).

    So if a system implements exceptions, and they are TRULY exceptional circumstances that need handling, then FORCE it I say. If you’re not going to force them, then just don’t have them. They are not practical IMO. I understand this could be labelled an issue of “laziness” that needs to be addressed some other way but I do think it is a real-world issue that impacts real projects.

    All that said, in my experience I have found return codes to be perfectly manageable. You know what’s happening when.

  2. Andrew

    Aaron, what you say is correct but return codes aren’t that manageable in the long run IMHO. Adding a new exception to already exception-safe code is easy and safe, if you modify code to return a new return code all your error checking code needs to be updated.

    Both exceptions and return codes form part of the contract for a function. The difference is that (well-designed) exceptions can be modified and added to without changing client code and allow error handling code to be consolidated.

    At least in theory…

  3. Edouard

    Nice work Andrew. Myself, I’ve never felt confident enough about exceptions to publicly state my opinions on them; it’s still an area I’m really hesitant about.

    I’m not sure I agree about networking exceptions – I’ve always felt that a packet not being delivered (or whatever) is an expected event, not anything exceptional, and that the programmer should have code in place to simply handle that. You sort of say that, but by using exceptions as the delivery mechanism…

    Aaron’s point is really valid I feel though – the program scope over which exceptions can work (crossing the usual bounds of locality that code and data are supposed to have in OO design) exposes the lack of standardisation on both exceptions and exception-safe code that vendors, third-parties, my co-workers and even I write. This is the big win in Java: the decisions about how exceptions are used has already been made for the programmer. C++ can write code that can’t be written in Java for this reason, but on the other hand, any Java library from any third-party will more or less fit snugly into you monster enterprise codebase. This, as we all know, is the complete opposite in C++; the decisions made by the library, across multiple orthogonal dimensions, almost prohibit a library’s use in a complex C++ application without some (often major) code-wrapping work.

    I also worry about free pointers (Nigel Bree swore against exceptions, but he actually *had* a garbage collector to address the main problem with heap allocated data that ever gets stored in plain old pointers, even for the shortest time), and just the general problem of knowing what has been invalidated by the exception. If I end up catching an exception some distance from the throw point (or, god help me, in a separate thread), how do I know what objects have been in some way modified between the try and catch points in my program? I know some complex operation I attempted failed, but did any of that code modify my programs state? Is there invalid data or state lying in wait in the vast array of objects that tree of execution may have interacted with?

    In a functional language I know I’m OK, and even languages that place a heavy emphasis on transforming copies of data rather than modifying the original can at least reduce that burden, but, again, C++ is definitely not that kind of language.

    My most wished-for programming language feature is transactions, and this is one of the reasons why. If I had those I could roll my entire program state back to a known point after an exception, and I could be guaranteed to know I’m still running with a valid application (there might be some, uhh, performance penalty in doing so – but there ain’t no free lunch, as the saying goes). No wonder one of the things that Andrei Alexandrescu has written about is transaction-like stack object to automatically roll back state on abnormal function/method exit.

    Crikey – that got away on me a bit!

  4. Andrew

    The loss of a single packet is perhaps not exceptional, but if I am writing to a TCP stream and it suddenly returns Connection Reset then I think throwing is not a bad choice, particularly if you are writing to the stream from several places in your code. You should have all your socket functions wrapped up in a C++ wrapper, so it is not too much to ask that anything that uses the wrapper be prepared to handle exceptions.

    It is the same with 3rd-party code; normally you have to wrap it with a C++ wrapper anyway so arranging things to behave correctly vis-à-vis exceptions is usually not too onerous. One of the things a like about exceptions is that you can have layered error handing, which answers your point about modified program state. You can do things like: modify the state, write to a file, catch a file-exception and undo your modification in the same function that you made it then rethrow the same exception to the outer layer of your program can deal with the problem. The same exception can be dealt with and rethrown in several different layers, each cleaning up after itself if required.

    That said, there are reasons why exceptions might not be a good choice. They do have some runtime overhead, although modern implementations have very little cost if the exception is not thrown. Older codebases with lots of C-style memory allocation and file handling can be impossible to retrofit for exception-safety.

    My main argument for exceptions is that you are using them anyway. Modern C++ with STL and boost throws exceptions and you have to work quite hard to get it to stop. If you are already paying the cost in runtime overhead and being prepared to catch exceptions, then you might as well get the benefits.

  5. Andrew

    I forgot to mention that I agree with you about Java – I thought the checked exceptions were the best thing about the language. Nobody else seems to agree, but I think they made the right choice.

Comments are closed.