3

链接http://gotw.ca/gotw/066.htm指出

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

虽然http://www.parashift.com/c++-faq-lite/exceptions.html#faq-17.8

如果构造函数抛出异常,则对象的析构函数不会运行。如果您的对象已经完成了一些需要撤消的操作(例如分配一些内存、打开文件或锁定信号量),则该“需要撤消的操作”必须由对象内部的数据成员记住。

这两个说法不矛盾吗?第一种暗示构造函数中的 try catch 几乎没有用,而第二种暗示需要释放资源。我在这里想念什么?

4

5 回答 5

2

对简单术语的模糊翻译是: function-try-blocks 只能用于翻译异常始终使用 RAII,并且每个资源都应由单个 object 管理,它们并不矛盾。哦,好吧,翻译不完全是这样,但争论最终导致了这两个结论。

来自 C++FAQ lite 的第二个引用声明不会为构造函数未完成的对象调用析构函数。这反过来意味着,如果您的对象正在管理资源,并且当它管理多个资源时更是如此,那么您将陷入困境。您可以在异常逃离构造函数之前捕获该异常,然后尝试释放您获得的资源,但要这样做,您需要知道实际分配了哪些资源。

第一个引用说构造函数中的函数 try 块必须抛出(或重新抛出),因此它的用处非常有限,特别是它唯一有用的事情就是翻译异常。请注意,函数 try 块的唯一原因是在初始化列表执行期间捕获异常。

但是等等,函数 try 块不是处理第一个问题的方法吗?

嗯...不是真的。考虑一个有两个指针并在其中存储内存的类。并考虑第二次调用可能会失败,在这种情况下,我们将需要释放第一个块。我们可以尝试以两种不同的方式来实现它,使用函数 try 块,或者使用常规的 try 块:

// regular try block                       // function try block
struct test {
   type * p;
   type * q; 
   test() : p(), q() {                     test() try : p( new int ), q( new int ) {
      try {
          p = new type;
          q = new type;
      } catch (...) {                      } catch (...) {
          delete p;                            delete p;
          throw;
      }                                    } // exception is rethrown here
   }
   ~test() { delete p; delete q; }
};

我们可以先分析一个简单的案例:一个常规的 try 块。第一个构造函数将两个指针初始化为 null(: p(), q()在初始化列表中),然后尝试为这两个对象创建内存。在两者之一中,new type抛出异常并进入 catch 块。什么new失败了?我们不在乎,如果失败的是第二个新的,那么它delete实际上会发布p。如果是第一个,因为初始化列表首先设置了两个指针,0并且调用delete空指针是安全的,所以delete p如果第一个 new 失败,这是一个安全的操作。

现在看右边的例子。我们已将资源分配移至初始化列表,因此我们使用函数 try 块,这是捕获异常的唯一方法。再次,其中一个消息失败了。如果第二个 new 失败,delete p则将释放在该指针中分配的资源。但如果是第一个 new 失败,p则从未被初始化,并且调用delete p是未定义的行为。

回到我松散的翻译,如果您使用 RAII 并且每个对象只有一个资源,我们会将类型写为:

struct test {
   std::auto_ptr<type> p,q;
   test() : p( new type ), q( new type )
   {}
};

在这个修改后的示例中,因为我们使用的是 RAII,所以我们并不真正关心异常。如果第一次new抛出,则不会获取任何资源,也不会发生任何事情。如果第二次抛出失败,那么p将被销毁,因为在第二次尝试之前p已经完全构建new(那里有序列点),并且资源将被释放。

所以我们甚至不需要 atry来管理资源......这给我们留下了 Sutter 提到的另一个用法:翻译异常。虽然我们必须抛出失败的构造函数,但我们可以选择我们抛出的内容。我们可能会决定我们要抛出一个定制,initialization_error而不管构造中的内部故障是什么。这就是函数 try 块的用途:

struct test {
   std::auto_ptr<type> p,q;
   test()  try : p( new type ), q( new type ) {
   } catch ( ... ) {
      throw initialization_error();
   }
};
于 2011-08-18T17:04:25.443 回答
2

道德 #1 谈论function-try-block和第二个陈述谈论正常的 try catch 块,两者明显不同。

您需要了解两者之间的区别才能理解这两个句子的含义。这个答案在这里解释了这一点。

于 2011-08-18T16:17:26.393 回答
2

它们指的是不同的东西。

第一个与功能try块有关,即try包含整个功能的块,并且在构造函数的情况下,还包括对基类和成员对象的构造函数的调用,即执行的东西在实际的构造函数体运行之前。

士气是,如果基类或类成员未能正确构造,则对象构造一定会失败并出现异常,因为否则您新构造的对象将处于不一致的状态,而基对象/成员对象的一半已构造。所以,这样的try块的目的必须只是翻译/重新抛出这样的异常,也许是记录事件。你不能以任何其他方式做:如果你没有明确地throw在你的内部catch,编译器会添加一个隐式throw;来防止“半构造对象”的可能性。

第二个是指在构造函数体内出现的异常;在这种情况下,它说您应该使用“常规”try块来捕获异常,释放您现在分配的资源然后重新抛出,因为不会调用析构函数。

请注意,这种行为是有道理的,因为析构函数的隐式契约,与任何其他非构造函数的成员函数一样,是它期望在一致状态的对象上工作;但是构造函数抛出的异常意味着该对象尚未完全构造,因此将违反此约定。

于 2011-08-18T16:20:49.707 回答
1

这两个说法不矛盾吗?

不,第二个基本上意味着如果构造函数抛出异常(即构造函数),则不会调用析构函数。第一个意味着 function-try-block 不会让异常从构造函数中出来。它在构造函数本身中捕获它,并在那里处理它。

于 2011-08-18T16:16:36.237 回答
1

首先,构造函数 function-try-block 与构造函数中的 try-catch 不同。

其次,说“需要撤消的东西必须由数据成员记住”与说“使用 try-catch 块来撤消事物”不同。

这两种说法之间没有矛盾,他们在谈论不同的事情。事实上,第二个根本不是在谈论 try-catch。

于 2011-08-18T16:21:37.970 回答