在C++中重复使用异常处理代码

18

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

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. 还有其他方法吗?

至少,你应该重新抛出异常 - 否则你的程序几乎肯定处于无效状态。 - user2100815
我建议删除所有的noexcept标签。 - Eljay
那为什么不在主函数中进行一次捕获和错误报告,而不是重复呢? - user2100815
@Rodrigo • 如果在不会抛出异常的函数中的内部块(比如在func2的上下文中)中抛出了异常,那么 std::terminate 将被调用。这意味着 run_treated 不会有机会处理异常。 - Eljay
@Eljay 为什么呢?只有在异常离开非抛出函数时才会调用 std::terminate,而不是当它在其中被抛出。 - Angew is no longer proud of SO
显示剩余5条评论
2个回答

18

你可以使用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();
  }
}
当您进入func1func2中的处理程序时,正在处理"当前异常"。 Lippincott的主体启动了一个新的try..catch块并重新抛出它。然后以集中的方式捕获适当的异常并进行处理。您还应注意,异常处理逻辑实际上并不是noexcept。 理论上可能会有未被您列表覆盖的异常。在这种情况下,根据您如何标记事物noexcept,将调用几个std::terminate函数。

7
一个人的高阶函数可能对另一个人来说是复杂/神秘的。我不否认这些方法是非常不同的。 - StoryTeller - Unslander Monica
8
对我来说,这看起来并不更加复杂。相当简单和清晰。它的额外好处是您不需要通过标头公开异常处理代码(尽管您可以添加另一层以解决此问题)。 - dgrine
@OnMyLittleDuck:你不需要通过头文件公开处理代码。你可以使用轻量级类型擦除(例如function_ref:https://vittorioromeo.info/Misc/p0792r1.html)。 - Vittorio Romeo
顺便说一句,对我来说,这看起来更加复杂,原因有多个:更多的样板文件(每个想要使用异常处理逻辑的函数必须自己包装在一个 try-catch 中,其中 catch 是对 Lippincott 的函数调用);我们需要一个 try { throw; }。我认为,使用高阶函数只是允许您为已经包装在单个 try...catch 中的“任意代码块”提供自定义点 - 这就是为什么我觉得它更直接了当的原因。 - Vittorio Romeo
2
@VittorioRomeo 对可调用对象进行类型抹除意味着您每次都要支付类型抹除的成本。这里的成本仅在异常代码路径上支付。 - T.C.
显示剩余4条评论

14

这是一个好的方法吗?

是的。它可以防止代码重复,并允许您通过传递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 {}等同于[]{};


如果出现像 "std::function 崩溃" 这样的异常,除了结束程序外,我无能为力,所以让程序自己调用 std::terminate。除此之外,我保证不会抛出其他任何异常。 - rodrigocfd
我知道[]{},只是在我重用的代码库中,我喜欢明确一些。 - rodrigocfd
1
如果 std::function 的构造函数抛出异常,它会在调用者的上下文中发生,而不是在 noexcept 的被调用者中。 - T.C.
由于func没有参数,所以需要使用std::forward吗?请参见此处的代码 - rodrigocfd

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接