17
\$\begingroup\$

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")
\$\endgroup\$
17
\$\begingroup\$

In the catch block, you can rethrow the exception. This can be done in a function:

int rc = 0;
try{
// statements that can throw
}catch(...){
    HandleException(rc, "Extra info to append to logged exception message");
}



void HandleException(int rc, std::string msg)
{
    try{
        throw;
    }catch(Poco::Exception &e){
        // ...
    }catch( const std::exception &e){
        // ...
    }
}
\$\endgroup\$
  • \$\begingroup\$ Great! I had no idea the exception could be rethrown inside a different function called from within the catch block and keep all its original data. This concept will suite my needs perfectly. \$\endgroup\$ – Allbite May 29 '11 at 4:31
  • 1
    \$\begingroup\$ I have never seen the re-throw used like that. \$\endgroup\$ – Martin York Sep 17 '14 at 7:59
5
\$\begingroup\$

This question is getting old, but there is actually an elegant way to create such an exception boundary: create a function as the one proposed by @Tim Martin, then wrap the old code into lambdas that will be called by the exception boundary handle. Here it the excception boundary handle:

template<typename Callable>
auto exception_boundary_handle(Callable&& func, const std::string& msg=""s)
    -> decltype(func())
{
    try
    {
        return func();
    }
    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);}
    }  
}

You can use it as follows:

int some_func(int eggs)
{
    return exception_boundary_handle([&] {
        // put the old code here,
        // I filled it with pseudo-random stuff
        eggs += 42;
        return eggs;
    },
    "Extra error message if you ever need it");
}

Of course, since this is not a macro solution, you will have to add another parameter if you want to inject the __FUNCTION__ information into the handler:

template<typename Callable>
auto exception_boundary_handle(Callable&& func,
                               const std::string& msg=""s,
                               const std::string& func_name="")
    -> decltype(func())
{
    try
    {
        return func();
    }
    // ...
    catch(...)
    {
        try{ LogCritical( Logs.System(), std::string( "Exception caught in "s + func_name + ". "s ).append( msg ) );}catch(...){assert(0);}
    }  
}
\$\endgroup\$
3
\$\begingroup\$

Well, there's a way of doing it with template functions, but I don't think I like it any more than your solution:

#include <iostream>

using namespace std;

template<typename P, typename Q, typename Ret>
Ret handle_all( Ret(*fn)(P, Q), P p, Q q) {
   try {
      Ret ret = fn(p, q);
      return ret;
   }
   catch (...) {
      cout << "Unexpected exception" << endl;
   }
}

template<typename P, typename Ret>
Ret handle_all(Ret (*fn)(P), P p) {
   try {
      Ret ret = fn(p);
      return ret;
   }
   catch (...) {
      cout << "Unexpected exception" << endl;
   }
}

int funky_function(int a, string b) {
   cout << "a=" << a << endl;
   cout << "b=" << b << endl;

   throw "wild exception";
   // Throwing strings is stupid, this is just an example...
}

int other_function(int a) {
   cout << "single parameter a=" << a << endl;

   throw "wild exception";
}

int main (void) {
   handle_all(funky_function, 12, string("some string"));
   handle_all(other_function, 16);
}

You need to declare a version of handle_all for each different number of function parameters you want to support, but not for every combination of types. That means that every time you change your exception handling you'll have to update more than one piece of code, but only a small number.

The syntax of calling the function to be wrapped is a bit unfortunate, in that the parameters to the function go in the same parameter list as the name of the function to be called. I think you could work around this by using a template functor class rather than a template method (with an overload for operator()).

Edit: Here's what the functor solution would look like:

template<typename P, typename Q, typename Ret>
class handle_all {
 public:
   handle_all(Ret (*fn)(P, Q)) : fn(fn) { }

   Ret operator()(P p, Q q) {
      try {
         Ret ret = fn(p, q);
         return ret;
      }
      catch (...) {
         cout << "Unexpected exception" << endl;
      }
   }

 private:
   Ret (*fn) (P, Q);
};

You have to call it like this:

handle_all<int, string,int> runner(funky_function);
runner(12, "some string");

Note that in this case, automatic construction of a temporary std::string from the string literal works as expected. Apart from that, it's not much of an improvement.

\$\endgroup\$
  • \$\begingroup\$ Like it, but the handler needs to encapsulate more than one function. I guess adding the concept of lamdas might allow it, but if anything inside that block happened to be a macro then the templating of it might bust if there's a macro inside the stuff it surrounds. \$\endgroup\$ – Allbite May 29 '11 at 4:17
  • 1
    \$\begingroup\$ The handler encapsulates any function with the given number of parameters, e.g. all 1-parameter functions. You need a new version of the handler for each possible number of function arguments, but hopefully there aren't that many cases of functions with lots of parameters. I'm not sure this is the code's biggest weakness, though. :-) \$\endgroup\$ – Tim Martin May 30 '11 at 17:42
  • \$\begingroup\$ Maybe you could make a single version using variadic templates \$\endgroup\$ – M.M Aug 30 '14 at 11:37

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.