对简单术语的模糊翻译是: 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();
}
};