Sending Exceptions in C ++

How should exceptions be sent so that error handling and diagnostics can be handled in a centralized, user-friendly manner?

For instance:

  • The DataHW class handles communication with some data collection hardware.
  • The DataHW class can throw exceptions based on a number of possible errors: intermittent signal, no signal, CRC failure, driver error. Each type of error gets its own exception class.
  • The DataHW class is called by a series of different pieces of code that perform various types of collection and analysis.

The correct error handling strategy depends on the type of exception and the operation being performed. (If the signal is intermittent, repeat X times, then inform the user about it, if there is a driver error, write down the error and restart the driver, etc.). How to activate this error handling strategy?

  • Recovering coding errors in each exception class: this will result in the exception classes being quite large and containing a high-level interface and system control code. That seems bad.
  • Providing a separate catch for each type of exception: since the DataHW class is called from different places, each catch must be duplicated at each call site. That seems bad.
  • Using a single catch that calls some ExceptionDispatch function with a giant RTTI-based switch : RTTI and switch usually indicate an inability to apply the OO design, but this seems like the least bad alternative.
+3
source share
4 answers

Avoid duplicate catch blocks on every call site by catching (...) and calling a common handler function that jumps and sends:

 f() { try { // something } catch (...) { handle(); } } void handle() { try { throw; } catch (const Foo& e) { // handle Foo } catch (const Bar& e) { // handle Bar } // etc } 
+5
source

The idea I come across is that exceptions should be caught by levels that can handle them. For example, a CRC error can be caught by a function that sends data, and after detecting this exception, it can try to retransmit, while a β€œno signal” exception can be detected at a higher level and discard or postpone the entire operation.

But I assume that most of these exceptions will be found around the same function. It is a good idea to catch and process them separately (as in soln # 2), but you say that it causes a lot of duplicate code (leading to soln # 3.)

My question is: if there is a lot of code for duplication, why not turn it into a function?

I think of the lines ...

 void SendData(DataHW* data, Destination *dest) { try { data->send(dest); } catch (CRCError) { //log error //retransmit: data->send(dest); } catch (UnrecoverableError) { throw GivingUp; } } 

I assume this will be similar to your ExceptionDispatch () function, only instead of switch ing for the type of exception, it itself wraps the exception-calling and catch exception itself.

Of course, this function is too simplified - DataHW may require a whole wrapper class; but, in my opinion, it would be nice to have a centralized point around which all DataHW exceptions are handled - the way the different users of the class will handle them is similar.

+2
source

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) { // 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 false; } } void sendData(char const *data, std::size_t len); void readData(char *data, std::size_t len); }; 

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).

+1
source

Perhaps you could write a wrapper class for the DataHW class? The shell will have the same functions as the DataHW class, but it also contains the necessary error handling code. The advantage is that you have the error handling code in one place (DRY principle) and all errors will be processed evenly. For example, you can translate all low-level I / O exceptions to higher-level exceptions in the wrapper. Basically preventing low level readings for the user.

As Butler Lampson said: β€œAll problems in computer science can be solved by a different level of indirection.”

+1
source

Source: https://habr.com/ru/post/1203006/


All Articles