mr-edd.co.uk :: horsing around with the C++ programming language

Refinements of my tips for exceptions design

[30th November 2008]

A short while ago (well, a few months back now — where does the time go?!), I listed 9 of the guidelines I like to use when designing exception classes. I still think they're generally fine, but I feel I should provide a fix for #7. I have also come up with a #10.

What was #7 again?

I said this:

7. Don't be afraid of big member objects

If you're anything like me (and it's quite possible/fortunate that you're not) you'll tend to assume that adding expensive members to exception objects isn't a good idea. I'm talking STL containers and other objects that might be expensive to copy. But if you think about it, there's really no reason to worry about this kind of thing.

One concern is that creating or copying such an object might result in a std::bad_alloc exception getting thrown. This is true, but it's likely that this would have happened soon anyway if your application is that low on memory.

There's a problem with that second paragraph. An exception object will typically get copied when it is thrown. If that copy operation itself throws, then we'll have two active exceptions, which is simply not allowed. It will cause your program to disappear in a cloud of smoke thanks to an invocation of std::terminate().

So it's important that an exception's copy operations do not throw. Of course, many objects such as STL containers and even std::strings can throw an std::bad_alloc when they're copied. How do we work around this?

The easiest way is to store the objects in a smart-pointer, such as boost::shared_ptr<> in the exception object. This guarantees that copies won't throw because copying a boost::shared_ptr<> doesn't throw.

There's still the possibility that actually constructing the object to throw as an exception might unleash a std::bad_alloc, or even something else. But that doesn't matter (half as much) because no exception is currently active and this won't force a call to std::terminate(). Like I said in the quote, it's perhaps rather likely that a std::bad_alloc would have been thrown soon anyway if your program is so low on resources.

Tip #10

So let's suppose you create an exception class, E1, and it inherits another exception class E0. We'd like to write a test to check that when an E1 is thrown, we can catch it via a reference to an E0.

Here's how I had often written such tests up until now:

#include <boost/type_traits/is_base_of.hpp>
#include <test_o_matic.hpp>

// ...
CHECK((is_base_of<E0, E1>::value));
// ...

Can you spot the error? No, the template arguments are in the correct order — it's something else!

What we're doing here is checking that E0 is a base of E1. This isn't quite the same thing as checking that we can catch an E1 as an E0! Why? If there's virtual inheritance somewhere in the chain, this first test will pass when our desired catching mechanism may fail. Consider:

#include <iostream>

struct E0 { virtual ~E0() { } };

struct Ea : E0 { };
struct Eb : E0 { };

struct E1 : Ea, Eb { };

int main()
{
    try { throw E1(); }
    catch (const E0 &) { std::cout << "caught an E0\\n"; }
    catch (...) { std::cerr << "caught something, but not sure what!?\\n"; }

    return 0;
}

Can you guess what is displayed when the program is compiled and executed? Give it a try.

That's right, the catch (const E0 &) doesn't actually catch the exception, even though an E0 really was thrown! Ea and Eb didn't use virtual inheritance so there's ambiguity in which E0 to catch! So the exception is allowed to carry on its merry way, to eventually get scooped up by the catch(...).

So tip #10 has two parts:

1. to remember to use virtual inheritance appropriately when defining exceptions 2. when writing tests for your exceptions hierarchy, test catchability, rather than inheritance relationships.

So the test should really be written as:

#include <boost/type_traits/is_base_of.hpp>
#include <test_o_matic.hpp>

// ...
E1 ex( /* constructor args */ );
THROWS(throw ex, E0);
// ...

Of course part 2 of the tip can be generalized to test what you mean, which is of course good advice, but not related to exceptions. It's a specific mistake that I had been making and I hope it was worth sharing.

The example given in tip #10 was adapted from Boost's page on Error and exception handling.

Comments

(optional)
(optional)
(required, hint)

Links can be added like [this one -> http://www.mr-edd.co.uk], to my homepage.
Phrases and blocks of code can be enclosed in {{{triple braces}}}.
Any HTML markup will be escaped.