rodrigocfd
rodrigocfd

Reputation: 8049

Reusing exception handling code in C++

I have these two functions, with duplicated exception treatment, which has the sole purpose of displaying an error message:

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());
  }
}

I could just handle std::exception and show a single generic message, but I want to be more specific, that's why I'm catching all possible exceptions.

I want to reuse this exception treatment code. I thought about this:

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();
}
  1. Is this a good approach?
  2. If so, run_treated will be called a lot. Should I be concerned about performance?
  3. Any other approaches?

Upvotes: 18

Views: 1917

Answers (2)

There's the option of using a Lippincott Function to centralize the exception handling logic. Consider this:

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();
  }
}

How does it work? When you enter the handler in func1 or func2 there is a "current exception" being processed. The body of Lippincott starts a new try..catch block and re-throws it. Then it catches the appropriate exceptions and handles them accordingly in a centralized manner.

You should also note that your exception handling logic isn't really noexcept. There could theoretically be exceptions not covered by your list. In which case there are several places for std::terminate to be called, depending on how you mark things noexcept

Upvotes: 20

Vittorio Romeo
Vittorio Romeo

Reputation: 93324

Is this a good approach?

Yes. It prevents code duplication and allows you to easily customize behavior by passing in a lambda.


If so, run_treated will be called a lot. Should I be concerned about performance?

Yes. std::function is not a zero-cost abstraction. You should use a template parameter to pass the lambda without requiring type erasure.

template <typename F>
void run_treated(F&& func) noexcept {
  try {
    std::forward<F>(func)();
  } catch // ... all the catches go here
}

I discuss and benchmark various techniques to pass functions to other functions in this article: "passing functions to functions".

If you don't want to use a template to pass func, you can use something like function_ref (proposed for standardization P0792). An implementation is available here: function_ref.cpp.


Unrelated comments:

  • Those unconditional noexcept specifiers look fishy. Can you actually guarantee that no exception will ever escape those functions?

  • []()->void {} is equivalent to []{}.

Upvotes: 15

Related Questions