2015-07-14

Reporting errors in destructors

July 17, 2015:

My below suggestions are not good for C++11 in GCC, Clang, and probably VS 2015 and later.

See my follow-up post:

Errors in destructors: C++ and the case for aggregate exceptions



I'm having an argument with someone who thinks Effective C++ is the end-all and be-all of all things. Specifically: The book has a chapter on how exceptions in destructors are tricky. The guy appeals to this as an authority, and concludes you should never throw exceptions in destructors, ever.

Now, Effective C++ is a good book. I would recommend it to a beginner. If you're at a point where you need to read Effective C++, please do read it, get 3-5 more years of experience, and then give me a ring.

Shortly, though, the guy is wrong. All code has to be aware of exceptions. All code has to get them right. Destructors are just somewhat trickier, because they have to be more aware of them.

This doesn't mean you shouldn't throw from destructors. In fact, you should. The following code:

~SomeClass() noexcept(false)
{
    if (!CloseHandle(someHandle) && !std::uncaught_exception())
        throw SomeException(GetLastError());
}

... is much better, more solid, and more useful than this:

~SomeClass() { CloseHandle(someHandle); }

The former will detect problems when closing the handle, and will cause them to be known if opportunity permits. The latter will just ignore them.

But you shouldn't just throw willy-nilly. The following:

&& !std::uncaught_exception()

... is absolutely crucial. You shouldn't throw that exception if one is already in progress. If you do, your program will most likely abort. (But that is - by the way - not always a bad thing.)

The bugaboo about never throwing exceptions from destructors is harmful. You should throw from destructors. However, you need be aware of what you're doing.



July 16, 2015:

Unfortunately, since C++11, the language standard committee disagrees. Since C++11, with compilers that implement this, destructors that throw have to be explicitly marked noexcept(false). If not, the object has to inherit from, or contain another object with a destructor explicitly marked so. This is the case even if the destructor has obvious throw statements! With GCC 4.9.2, the following code:

struct A { ~A() { throw "all good"; } };

int main()
{
    try { A a; }
    catch (char const* z) { std::cout << z << std::endl; }
}

... compiles with no errors and no warnings with -std=gnu++11 (or 14) and -Wall, but results in std::terminate() being called.

Visual Studio 2013 does not support noexcept, so the above code outputs "all good". I have not tested 2015. In GCC, and I believe also Clang, any throwing destructors must be declared noexcept(false).

No comments: