Why You Shouldn’t Throw in Destructors

In my post two weeks ago, What’s the Point of Function Try Blocks, I mentioned that you shouldn’t throw in destructors. Why exactly is this?

Imagine the following:

void f() {
  MyClass c;

If functionThatMightThrow throws, stack unwinding occurs. What this means is basically that all the objects on the stack get destructed, from the innermost block and out. In this case, c is destroyed before the exception is thrown out of f.

But what happens if ~MyClass() throws? There would now be two simultaneously active exceptions, something that luckily is forbidden by the standard. Instead, terminate() is called, and the whole program stops.

So what can you do? Design around it. Log and swallow. Ignore. Abort. If your class is managing some resource that should be cleaned up, but failing to do so is not critical, you could also provide a close() method that throws on error. A client who feels it is necessary to be 100% sure the resource is cleaned up will then have the option of calling close() and handling the exception himself, before your destructor is called.

Note that relying on the client to remember to call close(), setUp(), init() etc. is generally a bad idea, but might be acceptable in some cases. What makes it acceptable in this case is if cleaning up the resource is optional.

What’s the Point of Function Try Blocks

This post is a follow up to The Returning Function that Never Returned, which I wrote a couple of months ago. You can read it first if you want, it is only around a hundred words, but this post can be read on its own as well.

In my previous post, I presented a function with try outside of the braces. This is called a “function try block”. The example went like this:

std::string foo() try {
    return "foo";
} catch (...) {
    log("Unable to foo!")

This is completely useless, even dangerous. The try should be moved inside the function itself, like this:

std::string foo() {
    try {
        return "foo";
    } catch (...) {
        log("Unable to foo!")
        //either rethrow or throw something else
    //or make sure something is returned

So why were function try blocks introduced to the language in the first place? Have a look at this:

class A : public B{
    A(int x) : c(x){}
    C c;

How do you catch an exception thrown by Cs constructor? Having try inside the function block is too late. Initializing c inside the function block is not a solution either, since c will always be initialized before the body of the constructor, even if it is not mentioned in the initializer list. The only way to catch such an exception is to use a function try block on the constructor, and that is also why they were introduced. (Note that this argument is also valid if Bs constructor might throw.)

This is also the only sane case in which to use them. The other to candidates are regular functions and destructors, both of which I will get back to in a moment.

First, let’s have a look at what you can do with a function try block on a constructor. When the catch block reaches its end, it will rethrow the exception. It is impossible to swallow it. If you could swallow the exception, the code that tried to construct an object of this type would have no way of knowing that construction failed. A failed construction means the object doesn’t exist, and it doesn’t make sense to continue pretending nothing has happened. The only thing you can do is to throw an exception of another type, or cause a side effect (such as logging) and then retrhow. In particular, you cannot try to recover from the problem.

The same goes for destructors, you cannot swallow the exception. And since you really should avoid having destructors throw, using function try blocks on destructors is a bad idea.

Try blocks on regular functions behave a bit differently; if the end of the catch block is reached, the function will automatically return. But if you have a non-void function, this doesn’t make sense at all, as I mentioned in my previous post.

So in conclusion:

  1. Only use function try blocks for constructors.
  2. Don’t try to do anything else than rethrowing (possibly another type) or cause side effects like logging.

For a more in-depth discussion of this, have a look at Sutter’s Mill: Constructor Failures (or, The Objects That Never Were) by Herb Sutter. If you would like a recap of exception handling and constructor initializers thrown in, I recommend to start with Introduction to Function Try Blocks by Alan Nash.