43

我最近遇到了这种try-catch用于函数的语法。

struct A
{
  int a;

  A (int i) : a(i)  // normal syntax
  {
    try {}
    catch(...) {}
  }

  A ()   // something different
  try : a(0) {}
  catch(...) {}

  void foo ()  // normal function
  try {}
  catch(...) {}
};

两种语法都有效。除了编码风格之外,这些语法之间是否有任何技术差异?其中一种语法在任何方面都优于其他语法吗?

4

4 回答 4

45

第一种语法:
try 块的作用域在成员初始化列表完成后开始,因此在成员初始化期间抛出的任何异常都不会被这个 try-catch 块捕获。

第二种语法:
它确保如果在成员初始化列表期间抛出异常,那么您能够捕获该异常。

第三种语法:
它确保从函数体内的 try 块的起始大括号之间抛出的任何异常都被适当地捕获,这意味着在参数传递期间引起的任何异常(如果可能发生)都不会在这个 try 中被捕获-catch 块。

所以是的,它们提供的功能明显不同。


编辑:
在构造函数和析构函数中使用第二种语法(function-try-block)时要考虑的一些准则:

根据 C++ 标准,

如果 catch 块没有抛出(重新抛出原始异常,或者抛出新的异常),并且控制到达构造函数或析构函数的 catch 块的末尾,则原始异常会自动重新抛出。

简单来说:
构造函数或析构函数-try-block 的处理程序代码必须通过发出一些异常来完成。

准则 1:
构造函数-try-block 处理程序只有一个目的——翻译异常。(也许是为了做日志记录或其他一些副作用。)它们对任何其他目的都没有用。

从析构函数中抛出异常是个坏主意,看看这里就知道为什么了。
准则 2:
析构函数尝试块根本没有实际用途。他们永远不应该检测到任何东西,即使由于恶意代码而需要检测到某些东西,处理程序也没有什么用处,因为它无法抑制异常。

准则 3:
始终在构造函数或析构函数体中的本地 try-block 处理程序中清理非托管资源获取,而不是在构造函数或析构函数函数try-block 处理程序中。


对于标准球迷:

C++ 标准,第 15.3 条,第 15 段:

如果 return 语句出现在构造函数的 function-try-block 的处理程序中,则程序格式错误。

C++ 标准,第 15.3 条,第 16 段:

如果控制到达构造函数或析构函数的函数尝试块的处理程序的末尾,则重新抛出正在处理的异常。否则,当控制到达 function-try-block (6.6.3) 的处理程序末尾时,函数返回。从函数try块的末尾流出相当于没有值的返回;这会导致值返回函数 (6.6.3) 中的未定义行为。


参考:
查看此必读资源以获取更多详细信息和解释。

于 2011-07-20T04:19:04.683 回答
9

Function-try-block主要在构造函数中很有用,因为在初始化列表中没有其他方法可以捕获异常。在析构函数中,必须小心在 catch 块中返回,因为异常会自动重新抛出。(并且在良好的设计中,析构函数一定不能抛出。)在普通函数中,这个特性是没有用的。编辑:一篇旧但仍然很好的文章:http ://drdobbs.com/184401316

于 2011-07-20T04:54:29.563 回答
3

不妨引用规范......或者至少是草稿

第 15 条第 (4) 款:

函数尝试块处理程序序列ctor-initializer(如果存在)和复合语句相关联。在复合语句执行期间抛出的异常,或者对于构造函数和析构函数,分别在类的子对象的初始化或销毁期间抛出的异常,以与抛出异常相同的方式将控制权转移到函数尝试块中的处理程序在try-block执行期间,将控制权转移给其他处理程序。

(这里的handler-seq是后面的东西,catch而 thecompound-statement是函数体。)

因此,构造函数或析构函数上的“函数尝试块”会捕获 ctor-initializers 以及子对象的构造或销毁引发的异常。

在构造函数或析构函数以外的函数上,它与简单地包装函数体相同。(好吧,据我从阅读规范中可以看出。)

有趣的功能,对我来说是新的。谢谢你提出来。

于 2011-07-20T04:28:46.970 回答
2

“不同的东西”示例将初始化列表的处理置于 try 块的范围内。

于 2011-07-20T04:18:11.703 回答