C++ Exception Handling

Tuesday, May 5, 2009

A while ago I made a passing remark on my blog that I prefer not to use exceptions in native code. Some readers asked me to justify this position and I’ve been a bit reluctant to do so only because it’s a lengthy argument that I’m sure will bring a lot of passionate responses that I don’t really have the time to deal with. I was reminded of this again last night when I walked past one of our bookshelves at home and picked up my copy of John Robbins’ excellent debugging book and noticed that it has a chapter on crash handlers within which John does a good job of covering many of the reasons why I don’t use exception handling in native code. If you’re interested in this topic I would encourage you to read John’s book.

Of course I still write “exception safe” code in the sense that I use “resource acquisition is initialization” or constructors and destructors to scope resources, avoid side effects in functions, define invariants, etc. but this is just good practice to write simple and correct code whether or not exceptions are in play.

The Visual C++ compiler actually lets you disable exception handling in your code which by the way improves performance and reduces code size. Here’s how:

The story is very different for managed code where exceptions are very much an integral part of the runtime and thus cannot be avoided. Of course managed exceptions more than make up for the pitfalls with exception handling by providing a standard base class with exception information including the call stack at the time an exception was thrown thus greatly improving the ability for developers to track down bugs.

15 Comments

What about the C++ runtime?

Even if you disable exceptions in your own code, isn't there a possibility the runtime will throw exceptions?

Like, if you allocate more memory than the system can provide?

The default behavior for unhandled exceptions is to call terminate(), which usually isn't what you want.

Richard: keep in mind there’s a very big difference between exception handling and throwing exceptions.

By disabling exception handling you’re telling the compiler not to add any code to ensure objects are destroyed in the event that the stack is unwound due to an exception being thrown. Exceptions can still be thrown, whether by the operating system (SEH), by C++ libraries, or other libraries and runtimes such as the CLR. You can even catch them with SEH but you’re not going to get C++ semantics.

This obviously means you need to avoid libraries and functions that throw exceptions. Practically this means that STL is off limits, which is a big one for many people. I embraced C++ exceptions for many years before coming to the conclusion that they’re just more trouble than they’re worth (in native code).

The good news with C++ is that you can use whatever parts of the language, runtime services, and standard libraries that make sense for your application. In other words you don’t have to use those parts that rely on exceptions. You can and *should* still use RAII and smart pointers for correctness. I cannot stress that enough.

You mentioned memory allocation failure. Well it depends on what allocator you use. If you ask Windows to reserve some address space with VirtualAlloc it will return 0 on failure. You can also tell the compiler not to throw bad_alloc from the new operator and return 0 instead.

Whether or not you use exception handling you need to be very clear on how errors are reported and how you intend to handle them in your applications. Just using exceptions doesn’t take this burden of responsibility away although many developers seem to think so.

I have to comment that there isn't even the slightest mention as to why exception handling is bad other than the added code to handle stack unwinding. If that's even a slightly significant argument against the use of exceptions, then using exception handling is a slam dunk. The robustness and clarity it adds to code far outweigh any drawbacks to slightly increased code size. That's not to say that exception handling is a good way to control normal program flow. But, it is an excellent way to trap and handle... exceptions.

Cygnus_X-1: I thought I made it clear that the argument against exception handling was to be found in John's book and that I would not be repeating it. I'm in complete agreement that if you want to handle exceptions that C++ exception handling is a good way of handling them but that was not my point.

Yes, but I took that to mean that your full argument would be found there. That is, you wouldn't necessarily back up any claims made here because they are already in the book. But, could you at least make *some* claim however undetailed? I know, of course, not to try and debate it here because it's in the book. I just wanted to know where you were coming from.

Jonas: I love STL. I’ve used it for years and it’s great. For certain projects I continue to use it but a lot of the work I do these days is systems level programming, heavily using the Windows SDK. If I had to use STL I would now have to deal with two large frameworks with very different error handling models. If I’m working on a calculation-heavy project, say an analytics library for an investment back, then I’d almost certainly use STL.
Again, RAII does not itself require exception handling. Exceptions require exception handling. I agree that C++ without RAII is C but C++ without STL is still C++. &nbsp;:)

First you need to distinguish between different types of errors. You might have an error such as opening a file that doesn’t exist. This is likely an error that your program should handle automatically and is not “exceptional”. You might have an error such as using an invalid pointer or dividing by zero. This is always a bug in your code. These are raised as structured exceptions (SEH). Do you really want your application to continue running when this happens? I don’t think so. You’ve reached an unknown state in your program and the safest thing to do is to terminate immediately which is exactly what Windows will do for you.

The second thing you need to distinguish between is structured exception handling (SEH) and C++ exception handling. Although C++ exceptions are deep down implemented with SEH on Windows, SEH and C++ exceptions don’t work together and are very different beasts from a programming perspective. You could try to trap such exceptions but I’d rather they crash my program so that I have a nice minidump file for postmortem debugging.

There are some rare cases in older versions of Windows where you needed to handle SEH for certain API calls in low-memory conditions and you would do that directly without requiring C++ exception handling. But again this is very different to C++ exception handling and not at all compatible with it.

Absolutely, I want the process to crash BUT after my destructors are run.

Lets say i'm writing a "generic" transaction using RAII where I only commit after everything is completed and I rollback my changes in the destructors if not commited. Now I'm relying on my destructors to ALWAYS run otherwise I can't be in a consistent state after an exception.

This is a common pattern for me. I agree that memory allocations and open handles does not matter since they are closed and released after failing fast.

Jonas: If you’ve just had an access violation how can you be sure your application will be in a consistent state? Maybe the access violation was raised due to a corrupt stack. Do you know that your stack objects haven’t been corrupted and are safe to execute?
Anyway, based on what you’re saying what do you think this program will do? I assume you expect to see the message box. Give it a try.
#include &lt;windows.h&gt;
class Transaction{public:&nbsp;&nbsp;&nbsp; ~Transaction()&nbsp;&nbsp;&nbsp; {&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ::MessageBox(0, L"dtor", 0, MB_OK);&nbsp;&nbsp;&nbsp; }};
int main(){&nbsp;&nbsp;&nbsp; Transaction tx;&nbsp;&nbsp;&nbsp; int* p = 0;&nbsp;&nbsp;&nbsp; *p = 1;}
&nbsp;
This is why database engines have persistent transaction logs. C++ exception handling is not fault tolerant.