需要在构造函数中尝试catch

时间:2011-08-18 16:13:10

标签: c++ exception-handling constructor function-try-block

链接http://gotw.ca/gotw/066.htm说明了

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

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

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

这两个陈述不矛盾吗?第一种意味着构造函数中的try catch几乎没用,而第二种则表示需要释放资源。我在这里缺少什么?

5 个答案:

答案 0 :(得分:2)

道德#1谈论 function-try-block &第二个陈述谈到了一个正常的尝试捕获块,两者都截然不同。

你需要了解两者之间的差异,才能理解两句话是否有意义。 This 这里的回答解释了这一点。

答案 1 :(得分:2)

他们指的是不同的东西。

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

士气是,如果基类或类成员无法正确构造,则对象构造必须失败并出现异常,否则新构造的对象将处于不一致状态,与基础对象/成员对象一半构造。因此,此类try块的目的必须仅是翻译/重新抛出此类异常,可能是记录事件。您不能以任何其他方式执行操作:如果您未在throw中明确catch,则编译器会添加隐式throw;以防止“半构造对象”不测。

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

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

答案 2 :(得分:2)

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

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

第一个引用说构造函数中的函数try块必须抛出(或 rethrow ),因此它的用处非常有限,特别是它可以做的唯一有用的事情是翻译例外。请注意,函数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; }
};

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

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

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

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

在这个修改过的例子中,因为我们使用的是RAII,所以我们并不关心异常。如果第一个new抛出,则不会获取任何资源且没有任何反应。如果第二次投掷失败,则p将被销毁,因为p已经完全构造,然后尝试第二次new(那里有序列点),资源将被释放。

所以我们甚至不需要try来管理资源......这让我们得到了Sutter提到的其他用法:翻译异常。虽然我们必须抛弃失败的构造函数,但我们可以选择我们抛出的东西。我们可能会决定抛出定制的initialization_error,而不管构造中的内部故障是什么。这就是函数try块可以用于:

struct test {
   std::auto_ptr<type> p,q;
   test()  try : p( new type ), q( new type ) {
   } catch ( ... ) {
      throw initialization_error();
   }
};

答案 3 :(得分:1)

  

这两个陈述不矛盾吗?

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

答案 4 :(得分:1)

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

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

这两个陈述之间没有矛盾,他们谈论的是不同的事情。事实上,第二个根本就不是在谈论试用。