Есть три способа решить эту проблему.
Написание функций-оболочек
Напишите функцию-обертку для каждой функции, которая может генерировать исключения, которые будут обрабатывать исключения. Эта обертка затем вызывается всеми вызывающими, а не исходной функцией броска.
Использование функциональных объектов
Другое решение состоит в том, чтобы использовать более общий подход и написать одну функцию, которая принимает объект функции и обрабатывает все исключения. Вот пример:
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);
};
Теперь, если вы хотите что-то сделать, вы можете просто сделать это:
void doit() {
char buf[] = "hello world";
hw.executeAndHandle(boost::bind(&DataHW::sendData, _1, buf, sizeof buf));
}
Поскольку вы предоставляете функциональные объекты, вы также можете управлять состоянием. Допустим, sendData обновляет len, чтобы он знал, сколько байтов было прочитано. Затем вы можете написать функциональные объекты, которые читают и пишут, и ведут подсчет количества прочитанных символов.
Недостатком этого второго подхода является то, что вы не можете получить доступ к результирующим значениям бросающих функций, так как они вызываются из оболочек объекта функции. Нет простого способа получить тип результата связующего объекта функции. Одним из обходных путей является написание объекта функции результата, который вызывается executeAndHandle после успешного выполнения объекта функции. Но если мы вложим слишком много работы в этот второй подход только для того, чтобы выполнить всю работу по ведению домашнего хозяйства, это больше не будет стоить результатов.
Объединение двух
Есть и третий вариант. Мы можем объединить два решения (обертка и функциональные объекты).
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's false. for other integer
// types, it's 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);
};
Хитрость - это return f();
шаблон. Мы можем вернуться, даже когда f возвращает void. В конечном итоге это будет моим любимым, поскольку оно позволяет хранить центральный код дескриптора в одном месте, но также позволяет выполнять специальную обработку в функциях-оболочках. Вы можете решить, лучше ли разделить это и создать собственный класс, который имеет эту функцию-обработчик ошибок и оболочки. Вероятно, это было бы более чистым решением (я думаю о Разделение проблем здесь. Одна из них - это базовая функциональность DataHW, а другая - обработка ошибок).