Повторное использование кода обработки исключений в С++

У меня есть эти две функции с дублированным обработкой исключений, который имеет единственную цель - отобразить сообщение об ошибке:

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 {} эквивалентен []{}.