I'm using boost::exception. I like the usage pattern they suggest in their docs, I like the concept of having exception types as semantic tags to which I "append" arbitrary error info. In short, I find it brilliant.
However, I've hit a little problem - stack trace. I'd like something similar to Java's and C#'s stack trace, but I'm not ready to dive into
backtrace
or RtlCaptureStackBackTrace
. So, I've decided to create a poor man's stack trace. On the downside, it'll trace only my own code; on the upside, I hope it will be less problematic than a real stack trace (this opinion is not based on actual experience, but rather from what I've found online about it - e.g., this).
I didn't actually consider many alternatives for this one - I needed a structure of an abritrary size that could hold
BOOST_CURRENT_FUNCTION
, __FILE__
and __LINE__
. I've toyed with the idea of placing an std::vector in my exceptions. But I quickly realized that wasn't the way to go.
Why? Suppose we've got a function,
LowLevelFunction()
that throws a low_level_exception
, which has a "stack trace" vector (STV) where we place the error location. This is caught further up the chain by HighLevelFunction()
, which adds some more error info, adds its own location to the STV, and rethrows to its caller, HigherLevelFunction()
. Now, HigherLevelFunction()
was called by HighestLevelFunction()
, which doesn't really care about low_level_exception
s; it won't touch anything other than a high_level_exception
, except to send it directly to a logger (however, it does want the information contained in low_level_exception
). So, HigherLevelFunction()
throws a high_level_exception
, which has its own STV. Now what do we do? Copy low_level_exception
STV entries to high_level_exception
? Add HigherLevelFunction()
's location to low_level_exception
's STV? Use both STVs? There's no good solution, because the idea isn't sound.
However, as we said,
HighestLevelFunction()
wants all the relevant info contained in low_level_exception
. And all this info must end up on a high_level_exception
, since that's what it expects to catch. And the best solution I've found for this is nested exceptions, which is business-as-usual in Java and C#. And, thankfully, also in boost::exception.
We start with this typedef (from here):
typedef boost::error_info<struct tag_nested_exception, boost::exception_ptr> nested_exception.And that means we can now do something like this:
void LowLevelFunction() { ... BOOST_THROW_EXCEPTION(low_level_exception() << error_id(id) << error_message(msg)); } void HighLevelFunction() { ... catch (low_level_exception& lle) { BOOST_THROW_EXCEPTION(low_level_exception() << resource_name(res_name) << nested_exception(current_exception())); } } void HigherLevelFunction() { ... catch (low_level_exception& lle) { BOOST_THROW_EXCEPTION(high_level_exception() << operation_value(oper_val) << nested_exception(current_exception())); } } void HighestLevelFunction() { ... catch (high_level_exception& lle) { // Finally, do something useful with all this info. // Hope the logger's still working... } }
So, on the downside, we create two instances of
low_level_exception
, where we could've used just one. I can live with that, until evidence shows me it's a bad idea.
Yes, I know, there will be times when this may only make matters worse - e.g., if we catch an std::bad_alloc because we ran out of memory, creating exceptions is not the way to go. However, in a situation like this, I'd say our system is most likely headed for a fresh new (re)start, and there's little we can do to change its course.
Next on the list: Take a look at the implementation of diagnostic_information(). I like the completeness of its output, but not so much its formatting, especially when dealing with nested exceptions.
On a side note, I've loved finding out about
current_exception()
. Finally, I can do something useful here:SomeImportantClass::~SomeImportantClass() { try { // Cleanup. May throw } catch (...) { // I'd like to log this, but what exactly did we catch? // current_exception() to the rescue } }
Oh, and I've been having some fun with perl, regex, and log files. I'll be diving into Boost.Regex soon, so I'm getting used to it in a more "dynamic" environment.