18

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?
10
  • At the very least, you should re-throw the exception - otherwise your program is almost certainly in an invalid state. Commented Dec 30, 2017 at 17:53
  • @NeilButterworth, consider func1 and func2 being at the base of the stack, right after main. There's nowhere to go. Commented Dec 30, 2017 at 17:55
  • I recommend removing all the noexcept tags. Commented Dec 30, 2017 at 17:55
  • Then why not do the catching and error reporting once, in main, rather than duplicating it? Commented Dec 30, 2017 at 17:57
  • @Eljay, why so? Commented Dec 30, 2017 at 17:59

2 Answers 2

22

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

Sign up to request clarification or add additional context in comments.

9 Comments

This honestly looks more convoluted/cryptic compared to an higher-order function.
@VittorioRomeo - One persons higher-order function is another persons convoluted/cryptic. I don't deny the approaches are vastly different.
This doesn't look more convoluted to me. Pretty straightforward and clean. It has the added benefit that you don't need to expose the exception handling code through a header (although you could add another layer to work around this).
@OnMyLittleDuck: you don't need to expose the handling code through an header. You can use lightweight type erasure (e.g. function_ref: vittorioromeo.info/Misc/p0792r1.html).
@VittorioRomeo Type-erasing the callable means that you pay the type erasure cost all the time, every time. The cost here is only paid on the exceptional code path.
|
16

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 []{}.

4 Comments

If an exception like "std::function crash" happens, there's nothing I can do other than end the program, so let the program call std::terminate, by itself. Other than that, I guarantee no other exceptions will be thrown.
I'm aware of the []{}, I just like to be explicit in reused codebases of mine.
If construction of the std::function throws an exception, it happens in the context of the caller, not the noexcept callee.
Since func has no arguments, is std::forward necessary? See code here.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.