Reputation: 8049
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();
}
run_treated
will be called a lot. Should I be concerned about performance?Upvotes: 18
Views: 1917
Reputation: 170173
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
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