C 回调中的 try catch(...) - 坏主意?

Posted

技术标签:

【中文标题】C 回调中的 try catch(...) - 坏主意?【英文标题】:try catch(...) in C callback - bad idea?C 回调中的 try catch(...) - 坏主意? 【发布时间】:2015-02-24 08:56:29 【问题描述】:

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

【问题讨论】:

如果doStuff() 的事件抛出异常,你的调用C 程序是否真的可以做些什么来继续?如果它无法解决异常,那么拥有异常 IMO 没有任何好处。知道您的handle_foo 永远不会抛出,这将使您的调用代码更易于阅读。 您必须防止任何异常离开从 C 函数调用的函数,并且尝试 catch 是这样做的方法。如果你需要你的异常“穿越”一个 C 层,你可以使用一个全局异常 ptr(并不是说这会是好的风格)。从本质上讲,这为您提供了更强大的 errno 机制,但是当您的 c 代码返回时,您仍然必须手动检查错误。 Furthermore, I lose some useful features such as the ability to find out what was thrown。捕捉std::exception 并记录exception::what。不要抛出任何不是从std::exception 派生的东西。 【参考方案1】:

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

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

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

【讨论】:

@MatsPetersson:谢谢。 感谢您确认我的怀疑。因此,捕获所有异常不仅是一个好主意,而且实际上是必需的。如何确定异常的性质?除了将捕获物分解成不同的情况之外,没有通用的方法吗? @nneonneo 您可以在回调中记录或以其他方式显示异常详细信息。您也可以将详细信息隐藏在某种全局变量中 - 这就是 COM SetErrorInfo() 所做的。 @nneonneo:好的库要么从std::exception 派生它们的异常(直接或间接),要么在它们自己的异常层次结构中拥有一个或最多几个基类......一般来说你可以只需为这些基类提供明确的捕获案例,然后使用它们的虚函数来获取错误的详细信息。如果这变得非常冗长并且必须在代码中的数百个地方重现,请考虑添加一个额外的函数层来映射回合理数量的异常,或者 - 最后的手段 - 依靠宏:-(。 【参考方案2】:

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;

【讨论】:

以上是关于C 回调中的 try catch(...) - 坏主意?的主要内容,如果未能解决你的问题,请参考以下文章

javascript Try-Catch异步回调

javascript Try-Catch同步回调

try catch 小结 , node的回调callback里不能捕获异常 , 不能被v8优化(现在能了),

aspx页面使用ajax遇到try catch中使用Response.End()报错

水能详细讲解一下java 中的异常处理 try catch throw

try catch finally