Повторное использование кода обработки исключений в С++
У меня есть эти две функции с дублированным обработкой исключений, который имеет единственную цель - отобразить сообщение об ошибке:
void func1() noexcept {
try {
do_task();
do_another_task();
} catch (const std::out_of_range& e) {
show_msg("Out of range error", e.what());
} catch (const std::logic_error& e) {
show_msg("Logic error", e.what());
} catch (const std::system_error& e) {
show_msg("System error", e.what());
} catch (const std::runtime_error& e) {
show_msg("Runtime error", e.what());
} catch (const std::exception& e) {
show_msg("Generic error", e.what());
}
}
void func2() noexcept {
try {
do_something();
do_something_else();
do_even_more();
} catch (const std::out_of_range& e) {
show_msg("Out of range error", e.what());
} catch (const std::logic_error& e) {
show_msg("Logic error", e.what());
} catch (const std::system_error& e) {
show_msg("System error", e.what());
} catch (const std::runtime_error& e) {
show_msg("Runtime error", e.what());
} catch (const std::exception& e) {
show_msg("Generic error", e.what());
}
}
Я мог бы просто обработать std::exception
и показать одно общее сообщение, но я хочу быть более конкретным, поэтому я улавливаю все возможные исключения.
Я хочу повторно использовать этот код обработки исключений. Я подумал об этом:
void run_treated(std::function<void()> func) noexcept {
try {
func();
} catch // ... all the catches go here
}
void func1() noexcept {
run_treated([]()->void {
do_task();
do_another_task();
});
}
void func2() noexcept {
do_something();
do_something_else();
do_even_more();
}
- Это хороший подход?
- Если так,
run_treated
будет называться много. Должен ли я беспокоиться о производительности?
- Любые другие подходы?
Ответы
Ответ 1
Существует возможность использования функции Lippincott для централизации логики обработки исключений. Рассмотрим это:
void Lippincott () noexcept {
try {
throw;
} catch (const std::out_of_range& e) {
show_msg("Out of range error", e.what());
} catch (const std::logic_error& e) {
show_msg("Logic error", e.what());
} catch (const std::system_error& e) {
show_msg("System error", e.what());
} catch (const std::runtime_error& e) {
show_msg("Runtime error", e.what());
} catch (const std::exception& e) {
show_msg("Generic error", e.what());
}
}
void func1() noexcept {
try {
do_task();
do_another_task();
} catch (...) {
Lippincott();
}
}
void func2() noexcept {
try {
do_something();
do_something_else();
do_even_more();
} catch (...) {
Lippincott();
}
}
Как это работает? Когда вы вводите обработчик в func1
или func2
, обрабатывается "текущее исключение". Тело Lippincott
запускает новый блок try..catch и повторно бросает его. Затем он улавливает соответствующие исключения и обрабатывает их соответственно централизованным образом.
Следует также отметить, что ваша логика обработки исключений на самом деле не является noexcept
. Теоретически теоретически могут быть исключения, не включенные в ваш список. В этом случае есть несколько мест для вызова std::terminate
, в зависимости от того, как вы отмечаете вещи noexcept
Ответ 2
Это хороший подход?
Да. Он предотвращает дублирование кода и позволяет легко настраивать поведение, передавая в лямбда.
Если так, run_treated
будет называться много. Должен ли я беспокоиться о производительности?
Да. std::function
не абстракция нулевой стоимости. Вы должны использовать параметр шаблона, чтобы передать лямбда, не требуя стирания стилей.
template <typename F>
void run_treated(F&& func) noexcept {
try {
std::forward<F>(func)();
} catch // ... all the catches go here
}
Я обсуждаю и сравниваю различные методы для передачи функций другим функциям в этой статье: "передача функций в функции" .
Если вы не хотите использовать шаблон для передачи func
, вы можете использовать что-то вроде function_ref
(предлагается для стандартизации P0792). Реализация доступна здесь: function_ref.cpp
.
Несвязанные комментарии:
-
Эти безусловные спецификаторы noexcept
выглядят подозрительно. Можете ли вы на самом деле гарантировать, что ни одно исключение не сможет избежать этих функций?
-
[]()->void {}
эквивалентен []{}
.