在 C 回调中尝试{ } catch(...) { } - 坏主意?

try{ } catch(...) { } in C callback - bad idea?

我正在用 C++ 实现回调,它将从普通 C 代码调用。我的 main() 函数已经是 C++,但 C 代码将负责创建最终调用我的回调的线程。

现在我的回调看起来像

int handle_foo(void *userdata) {
    try {
        MyCPPClass *obj = static_cast<MyCPPClass *>(userdata);
        obj->doStuff();
        return 0; // no error
    } catch(...) {
        LogError("doStuff failed"); 
        return -1; // error
    }
}

这工作正常,但对我来说似乎很奇怪。此外,我失去了一些有用的功能,例如找出 什么 被抛出的能力(无需向我的每个回调添加大量额外的 catch 语句)。

这里 try {} catch(...) {} 是否合理,或者是否有更好的方式来编写我的 C 回调?

是的,您必须捕获异常并希望将它们转化为有用的东西。让异常通过 C 代码传播会导致未定义的行为。充其量您不能期望 C 代码保持一致的程序状态。

有关简单示例,请参阅 this answer。一个更难的例子是一些复杂的软件,如 SQLite - C 代码将获取一些互斥体并且不会释放它,因为异常只是 "flies through" 并且你的程序现在是 toast.

此外,如果所有代码都是针对相同的 C++ 运行时构建的,这也有可能 "working"。如果你碰巧在 Visual C++ 9 中实现了回调,而 Visual C++ 10 中的其余代码或这些部分是针对静态运行时库编译的——你现在有两个不同的运行时,回调中未处理的异常导致 terminate() 被调用。

sharptooth 回答了你的大部分问题,James McNellis 写了一篇很好的 blog post 关于如何使用现代 C++ 优雅地摆脱样板文件。

它的要点是有一个像这样使用的 translate_exceptions 函数:

int callback(...) {
   return translate_exceptions([&]{
      // ... your code here
   });
}

translate_exception 是一个简单的函数模板(使用函数级别的 try 块)接受一个可调用对象:

template<typename Fn> int translate_exception(Fn fn) try {
  fn();
  return 0;
} catch(std::exception const& e) {
  LOG("[EE] exception ", e.what());
  return -2;
} catch( ... ) {
  LOG("[EE] unknown exception");
  return -1;
}