There are three ways to solve this problem.
Writing Wrapper Functions
Write a wrapper function for each function that can throw exceptions that would handle the exceptions. Then, all callers call this shell, instead of the original throwing function.
Using function objects
Another solution is to use a more general approach and write a single function that takes a function object and handles all exceptions. Here is an example:
class DataHW { public: template<typename Function> bool executeAndHandle(Function f) { for(int tries = 0; ; tries++) { try { f(this); return true; } catch(CrcError & e) {
Now, if you want to do something, you can simply do this:
void doit() { char buf[] = "hello world"; hw.executeAndHandle(boost::bind(&DataHW::sendData, _1, buf, sizeof buf)); }
Since you provide function objects, you can also control the state. Let's say sendData updates len so that it knows how many bytes have been read. Then you can write function objects that read and write and support counting the number of characters read.
The disadvantage of this second approach is that you cannot access the resulting values ββof throwing functions, since they are called from wrappers of function objects. There is no easy way to get the result type of a function object binding. One way is to create a result function object that is called by executeAndHandle after the function object is executed. But if we include too much work in this second approach to do all the cleaning work, it is no longer worth the results.
Union of two
There is a third option. We can combine two solutions (shell objects and functions).
class DataHW { public: template<typename R, typename Function> R executeAndHandle(Function f) { for(int tries = 0; ; tries++) { try { return f(this); } catch(CrcError & e) { // handle crc error } catch(IntermittentSignalError & e) { // handle intermittent signal if(tries < 3) { continue; } else { logError("Signal interruption after 3 tries."); } } catch(DriverError & e) { // restart } // return a sensible default. for bool, that false. for other integer // types, it zero. return R(); } } T sendData(char const *data, std::size_t len) { return executeAndHandle<T>( boost::bind(&DataHW::doSendData, _1, data, len)); } // say it returns something for this example T doSendData(char const *data, std::size_t len); T doReadData(char *data, std::size_t len); };
The trick is the return f(); pattern return f(); . We can return even when f returns a void. Ultimately, this will be my favorite, because it allows you to simultaneously save the processing code in one place, and also allows you to perform special processing in the wrapper functions. You can decide whether it is better to separate this and create your own class that has this error handler and wrapper function. This would probably be a cleaner solution (I think Separation of problems is here. One of the main functions of DataHW and one is error handling).