I have this set of legacy C++ projects with a large number of public functions. At the start, none of those publicly exposed functions had try..catch
insulation inside them. When a C++ exception fired across that boundary, if the caller wasn't compiled with the same C++ compiler and the same project settings, then it easily caused a crash.
To help insulate against this problem I first went into every public function and wrapped the public function bodies with all-encompassing try...catch
blocks, with the catch
attempting to log a message about the exception. Basically just handling the (...
) case and logging "Unexpected Exception".
With hundreds of public functions (working up to thousands), this became tedious when I decided to add specialized std::exception
handlers to them all, other dev team members update some to do something different, etc.
In the interests of DRY (Don't Repeat Yourself), I chose to make a generic catch block which gets applied in those instances where I wanted something fast to insulate a function from throwing an exception but get as much info as possible into the log.
C++ exceptions being what they are, I couldn't figure out a nice simple portable way to do it without using a macro. Yes, yes, sad but true, another macro is born, but I am interested to learn about any other options considering it must be portable and fast. I don't mean to say macros are portable and fast, but this implementation fits the bill...
#define CatchAll( msg ) \
catch( const Poco::Exception &e ) \
{ \
try{ LogCritical( Logs.System(), std::string( e.displayText() ).append( msg ) );}catch(...){assert(0);} \
} \
catch( const std::exception &e ) \
{ \
try{LogCritical( Logs.System(), std::string( e.what() ).append( msg ) );}catch(...){assert(0);} \
} \
catch(...) \
{ \
try{ LogCritical( Logs.System(), std::string( "Exception caught in " __FUNCTION__ ". " ).append( msg ) );}catch(...){assert(0);} \
}
So there you have it. The code above gets called like this...
try{
// statements that can throw
}
CatchAll("*Special extra message about what was attempted in the try block*")
So that's it, that's the magic. This isn't meant to replace an intelligently coded block of specific exception handling, this is meant to quickly put bare-bones insulation where none existed before, and do it in a DRY, portable, fast and easy to grok way.
Ok, ok, macros are evil, I know, but what else could be done here?
And as goes the way of the macro, they proliferate and multiply. Here's a secondary macro to set an rc code in addition to logging, so the function can return a failing rc if an exception throws in the insulated function...
/// Catch all generic exceptions and log appropriately.
/// Logger is insulated from throwing, so this is a NO THROW operation.
/// Sets rc (integer parameter) so wrapping function can perform cleanup
/// before returning the error number.
#define CatchAllSetRC( rc, msg ) \
catch( const Poco::Exception &e ) \
{ \
(rc) = -1; \
try{ LogCritical( Logs.System(), std::string( e.displayText() ).append( msg ));}catch(...){assert(0);} \
} \
catch( const std::exception &e ) \
{ \
(rc) = -1; \
try{ LogCritical( Logs.System(), std::string( e.what() ).append( msg ));}catch(...){ assert(0); } \
} \
catch(...) \
{ \
(rc) = -1; \
try{ LogCritical( Logs.System(), std::string( "Exception caught in " __FUNCTION__ ". " ).append( msg ));}catch(...){ assert(0); } \
}
This expanded version gets called with an rc code so the caller can return it...
int rc = 0;
try{
// statements that can throw
}
CatchAll(rc, "Extra info to append to logged exception message")