In part I I showed how to use the cppx library’s exception translation support, which decouples the specification of how non-standard exceptions should be translated, from each routine’s invocation of such translation. The translation can be customized by dynamically installing and uninstalling exception translator routines. And essentially each routine that wants exception translation must use a catch
(this will most often be a generic catch(...)
) where it invokes cppx::rethrowAsStdX
, which in turn invokes the installed exception translator routines and performs a default translation if none of them apply.
In this second part I discuss how that translation machinery works.
In part III I’ll discuss the support for installation and uninstallation of exception translator routines. And perhaps I’ll need a part IV to discuss the cppx exception types! Anyway, now, diving down into the code…
The translator routine design.
There is no portable C++98 way that generic library code such as cppx::rethrowAsStdX
can itself catch exceptions of arbitrary unknown types and determine their types. Thus, specifying a translation as data (e.g. std::type_info
) that maps an original exception type to a std::exception
based type, is not possible. Hence, to customize the translation the client code installs exception translator routines that rethrow, catch and throw, like …
An exception translator routine (supplied by client code):
void xercesXTranslator( cppx::Rethrower& reThrower ) { try { reThrower.rethrow(); } catch( X666 const& ) { cppx::throwX( reThrower.prefix() + "Xerces exception" ); } }
The cppx::Rethrower
argument mainly serves to avoid one rethrow per exception translator routine, since rethrows are costly. Each exception translator routine must abide by a protocol where it calls cppx::Rethrower::rethrow
to rethrow, which does not perform an immediate rethrow. Instead it calls the next installed exception translator routine, and so on, until the last one where the single original exception rethrow is performed.
The main idea here is that with a modern C++ compiler setting up a try
–catch
is of very low cost, perhaps zero cost, while a rethrow-and-catch is costly. Essentially the calls result in a set of nested try
–catch
’es (low/zero cost), with the single original exception rethrow performed within the innermost try
. This works for well-behaved exception translator routines, because a well-behaved exception translator routine
- does not catch
std::exception
, - does not catch
cppx::IRethrowableException
, and - only throws
std::exception
(or derived classes).
Thus, whatever exception that a well-behaved exception translator routine throws it will be a std::exception
, which will not by caught by any other well-behaved exception translator routine.
std::logic_error
is a special case because it indicates a bug, and thus should be handled as early as possible, therefore preferably also by the exception translation. But since it is a std::exception
no well-behaved exception translator routine can handle it! Or, it could, by terminating, but in order to support whatever the client code’s wishes are it’s handled separately via a special installable handler.
The top level cppx::rethrowAsStdX
routine
In addition to std::logic_error
the top level cppx::rethrowAsStdX
handles an unknown exception separately, also via a special installable handler, since an unknown exception may also indicate a bug, and since it has to be handled in some way in order to guarantee that only std::exception
s will propagate up from the call:
In file [progrock/cppx/exception/throwing.h]:
namespace progrock { namespace cppx { class XTranslators { friend class Rethrower; // ... }; class Rethrower { friend inline void rethrowAsStdX( std::string const& ); // ... }; inline void rethrowAsStdX( std::string const& prefix = "" ) { XTranslators& translators = XTranslators::singleton(); try { Rethrower( prefix ).rethrow(); } catch( std::logic_error const& x ) { if( translators.logicXHandler() != 0 ) { translators.logicXHandler()( x.what() ); } throw LogicError( x.what() ); } catch( std::exception const& ) { throw; } catch( ... ) { if( translators.unknownXHandler() != 0 ) { translators.unknownXHandler()( "Uknown exception" ); } throw UnknownException(); } } } } // namespace progrock::cppx
Most of the work is delegated to Rethrower::rethrow
. Here at the top call level only the special cases are considered. It’s the only place where they can be considered, because only here can one catch a std::logic_error
thrown by one of the exception translator routines (say), and more importantly, only here does one know that a non-standard exception was not translated by one of the exception translator routines, and thus is “unknown”.
Now that I’m writing about it I see that the special handlers should perhaps receive also the prefix
, providing some context. It’s funny, but at least I (for one) think in a different way, I see different things, when I just code away and when I try to explain the code. So, assuming that this holds also for other people, I recommend trying to explain your code & design to others – if no other way, then just by trying to write about it for yourself! 🙂
But anyway, that change will have to wait, for it would mess up the usage example that I presented in part I!
As you can see from the code above std::exception
s are just rethrown so as to preserve the exact exception type. It is possible to support adding prefixes (dynamic context information) also for arbitrary standard exceptions while preserving their types, e.g. by way of a registration scheme. But to keep things simple – KISS! – I just rethrow them directly. However, the cppx library’s own exceptions, which of course are standard exceptions, support type-preserving rethrowing with addition of a text prefix. And to avoid adding a prefix to any such exception produced by a translator routine this is done at the bottom of the call chain where the single original exception rethrow is performed; this also makes life easier for a translator routine which can just always add the prefix, if practically possible.
How the translators are invoked
Now getting down to the Rethrower
class, and in particular Rethrower::rethrow
, it looks like this:
The
Rethrower
class, in file [progrock/cppx/exception/throwing.h]:
class Rethrower { friend inline void rethrowAsStdX( std::string const& ); private: typedef XTranslators::TranslatorFuncs::iterator Iterator; Iterator current_; Iterator end_; std::string prefix_; Rethrower( std::string const& prefix ) : current_( XTranslators::singleton().translators_.begin() ) , end_( XTranslators::singleton().translators_.end() ) , prefix_( prefix ) {} Rethrower( Rethrower const& ); // No such. Rethrower& operator=( Rethrower const& ); // No such. public: std::string const& prefix() const { return prefix_; } // Designed for use from within a translator func. void rethrow() { if( current_ == end_ ) { try { throw; } catch( IRethrowableException const& x ) { x.throwSelf( prefix_ ); } } else { XTranslatorFunc const f = current_->second; ++current_; f( *this ); } } };
The iterators here are iterators into a std::multimap
, hence the ->second
to access a translator routine.
Essentially, for a given call of cppx::rethrowAsStdX
there is a single stack-allocated Rethrower
instance that is passed by reference to the exception translator routines, each exception translator routine passing it on to the next via a call to Rethrower::rethrow
. At the end/bottom of this call chain the original exception is rethrown via a simple throw
. And if it is a cppx exception, supporting the IRethrowableException
interface, then it’s immediately caught and type-preservingly rethrown with added prefix text, if any.