链接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几乎没用,而第二种则表示需要释放资源。我在这里缺少什么?
答案 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块撤消事物”。
这两个陈述之间没有矛盾,他们谈论的是不同的事情。事实上,第二个根本就不是在谈论试用。