问题 在C ++中重用异常处理代码


我有这两个函数,有重复的异常处理,其唯一目的是显示错误消息:

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();
}
  1. 这是一个好方法吗?
  2. 如果是这样, run_treated 将被召唤 很多。我应该关注表现吗?
  3. 还有其他方法吗?

1770
2017-12-30 17:51


起源

至少,你应该重新抛出异常 - 否则你的程序几乎肯定处于无效状态。 - Neil Butterworth
@NeilButterworth,考虑一下 func1 和 func2 紧接着就在堆栈的底部 main。无处可去。 - Rodrigo
我建议删除所有 noexcept 标签。 - Eljay
那么为什么不主要捕获和错误报告一次,而不是重复它? - Neil Butterworth
@Eljay,为什么会这样? - Rodrigo


答案:


可以选择使用 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


6
2017-12-30 17:57



与高阶函数相比,这显然更复杂/更神秘。 - Vittorio Romeo
@VittorioRomeo - 一个人的高阶函数是另一个复杂/神秘的人。我不否认这些方法 大大 不同。 - StoryTeller
对我来说,这看起来并不复杂。非常简单和干净。它还有一个额外的好处,就是你不需要通过标题公开异常处理代码(尽管你可以添加另一个层来解决这个问题)。 - OnMyLittleDuck
@OnMyLittleDuck:您不需要通过标题公开处理代码。您可以使用轻量级擦除(例如 function_ref: vittorioromeo.info/Misc/p0792r1.html)。 - Vittorio Romeo
@VittorioRomeo Type-erasing callable意味着你每次都要一直支付类型擦除成本。此处的成本仅在特殊代码路径上支付。 - T.C.


这是一个好方法吗?

是。它可以防止代码重复,并允许您通过传入lambda轻松自定义行为。


如果是这样, run_treated 会被召唤很多。我应该关注表现吗?

是。 std::function 是  零成本抽象。你应该使用一个 模板参数 传递lambda而不需要类型擦除。

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 {} 相当于 []{}


7
2017-12-30 17:56



如果像“std::function 崩溃“发生了,除了结束程序之外别无他法,所以让程序调用 std::terminate, 通过它自己。除此之外,我保证不会抛出其他异常。 - Rodrigo
我知道了 []{},我只想明确重用我的代码库。 - Rodrigo
如果建设了 std::function 抛出异常,它发生在调用者的上下文中,而不是noexcept被调用者。 - T.C.
以来 func 没有争论,是 std::forward 必要?看到 代码在这里。 - Rodrigo