Monday, 20 July 2009

Taking Exception To Exceptions (Groan)

I've been reading Herb Sutter's More Exceptional C++ lately and it is - like #1 in the series - a long, hard read that leaves you even more worried about the code you write (and you should be: C++ will cut your throat in your sleep if you don't hold it arms length). I've just finished another section on exception safety (unsurprising given the title) and it answered a few questions that I've held on to from discussion with people who don't care about exception safety.

So, the main point made by those who don't believe in exception safety is:
"What's the point? Once the exceptions thrown, you're buggered anyway right?"
Wrong, the program is attempting to exit in a predicatable manner, carrying useful information away from the site of the exception to whatever might be waiting to catch it. Worse still, if you really don't care and you break the cardinal rule and allow an exception to subsequently leak from a destructor, then you cause the program to terminate. Not crash, terminate, meaning the run time just ends, the stack isn't unwound, resources aren't released (although the memory is passed back to the OS) and you run the serious risk of leaving anything you were interfacing with outside your own runtime in a seriously bad state.

Maybe the died-in-the-wool oponent of exceptions can still argue that the cost of writing exception safety into programs (and even worse, retro-fitting exception safety) is just not worth it to make a crash go more smoothly. If you absolutely don't care what your clients will experience when your app crashes, have no wish to gather error reports from the field, don't mind contributing to the odd blue-screen-of-death and don't care how much you compromise any future client code by undermining the fundamentals, then by all means continue.

However, exceptions are here to stay and there are some extremely simple practices we can adopt to protect the code we write and subsequent client code: learn about David Abraham's basic, strong and no-throw guarantees and especially, never allow an exception to escape from a destructor. Use the canonical form of assigment as much as you can (not just in assignment operators): Create a temporary, copy everything into that, then call a no-throw "swap" operation with this. Finally, use RAII everywhere. I can (looseley) quote Stroustrup to justify this one:
"The only code guaranteed to be called after an exception is thrown is the destructor of objects on the stack"
Basically, RAII is the only thing at your disposal to clean up if exceptions are thrown (apart from try-catch blocks obviously).

So, what am I rambling on about? Essentially it's that exceptions happen and if we want our programs to behave as intended in these circumstances - and maybe even do something sensible with the valuable information available - there are some rules we need to follow. These rules are simple and need considering up-front, not retrospectively, and are basically very good C++ programming patterns anyway.

No comments:

Post a Comment