问题 在C回调中尝试{} catch(...){} - 糟糕的主意?


我正在用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回调?


8874
2018-02-24 08:56


起源

如果事件发生了 doStuff() 抛出异常,如果你的调用C程序实际上可以做什么继续?如果它无法解决异常问题,那么拥有异常IMO就没有任何好处。拥有你的知识 handle_foo 永远不会抛出将使您的调用代码更容易阅读。 - TZHX
你必须防止任何异常离开从C函数调用的函数,并尝试使用catch是这样做的。如果你需要你的例外来“穿越”一个C层,你可以使用一个全局异常ptr(不是说这将是一个好的风格)。本质上,它为您提供了更强大的错误机制,但是当您的c代码返回时,您仍然需要ccheckmanually一个错误。 - MikeMB
Furthermore, I lose some useful features such as the ability to find out what was thrown。抓住 std::exception 和日志 exception::what。不要扔掉任何非衍生的东西 std::exception。 - sbabbi


答案:


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

看到 这个答案 举个简单的例子。一个更难的例子是使用一些复杂的软件,例如SQLite - C代码将获取一些互斥量并且不会释放它,因为异常只是“飞过”而你的程序现在是吐司。

如果所有代码都是针对相同的C ++运行时构建的,那么这也有可能“正常工作”。如果您碰巧在Visual C ++ 9中实现了回调,并且其他代码在Visual C ++ 10中编写或者这些部分是针对静态运行时库编译的 - 您现在有两个不同的运行时和回调中的未处理异常导致 terminate() 被召唤。


7
2018-02-24 08:59



“......不吐司”应该说“......现在敬酒”...... - Mats Petersson
@MatsPetersson:谢谢。 - sharptooth
谢谢你确认我的怀疑。因此,捕获所有异常不仅是一个好主意,它实际上是必需的。如何确定例外的性质?除了将捕获量分解为单独的案例之外,还没有通用的方法吗? - nneonneo
@nneonneo您可以在回调中记录或者显示异常详细信息。你也可以在某种全局变量中隐藏细节 - 这就是COM SetErrorInfo() 确实。 - sharptooth
@nneonneo:好的图书馆要么直接或间接地从他们那里得到例外 std::exception或者在他们自己的异常层次结构中有一个或者最坏的几个基类...通常你可以为这些基类设置显式的catch例子,然后使用他们的虚函数来获取错误的细节。如果它变得非常冗长,并且必须在代码中的数百个位置进行复制,请考虑添加一个额外的函数层,映射回合理数量的异常,或者 - 最后的手段 - 回退到宏:-(。 - Tony Delroy


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

它的要点是使用如下所示的translate_exceptions函数:

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

translate_exception是一个简单的函数模板(它使用一个函数级别的try块)来获取一个invokable:

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;
}

3
2018-02-24 11:30