可能重复:
函数try块什么时候有用
函数的try-catch语法之间的差异
在类UseResources
中构造Dog
对象时,此代码引发int
异常。int
异常被正常的try-catch
块捕获,代码输出:
Cat()
Dog()
~Cat()
Inside handler
#include <iostream>
using namespace std;
class Cat
{
public:
Cat() { cout << "Cat()" << endl; }
~Cat() { cout << "~Cat()" << endl; }
};
class Dog
{
public:
Dog() { cout << "Dog()" << endl; throw 1; }
~Dog() { cout << "~Dog()" << endl; }
};
class UseResources
{
class Cat cat;
class Dog dog;
public:
UseResources() : cat(), dog() { cout << "UseResources()" << endl; }
~UseResources() { cout << "~UseResources()" << endl; }
};
int main()
{
try
{
UseResources ur;
}
catch( int )
{
cout << "Inside handler" << endl;
}
}
现在,如果我们将UseResources()
构造函数的定义替换为使用function try block
的构造函数,如下所示,
UseResources() try : cat(), dog() { cout << "UseResources()" << endl; } catch(int) {}
输出是相同的
Cat()
Dog()
~Cat()
Inside handler
即具有完全相同的最终结果。
那么,function try block
的目的是什么呢?
想象一下,如果UseResources
是这样定义的:
class UseResources
{
class Cat *cat;
class Dog dog;
public:
UseResources() : cat(new Cat), dog() { cout << "UseResources()" << endl; }
~UseResources() { delete cat; cat = NULL; cout << "~UseResources()" << endl; }
};
如果Dog::Dog()
抛出,则cat
将泄漏内存。由于UseResources
的构造函数从未完成,因此对象从未完全构造。因此,它没有调用析构函数。
为了防止这种泄漏,您必须使用功能级别的try/catch块:
UseResources() try : cat(new Cat), dog() { cout << "UseResources()" << endl; } catch(...)
{
delete cat;
throw;
}
为了更全面地回答您的问题,构造函数中函数级try/catch块的目的是专门进行这种清理。函数级try/catch块不能吞下异常(常规块可以(。如果他们捕获了某个东西,当他们到达catch块的末尾时,他们会再次抛出它,除非您使用throw
显式地重新抛出它。你可以将一种类型的异常转换为另一种类型,但你不能只是吞下它,然后像它没有发生一样继续下去。
这也是为什么应该使用值和智能指针而不是裸指针的另一个原因,即使是作为类成员。因为,在您的情况下,如果您只有成员值而不是指针,则不必这样做。正是使用了裸指针(或RAII对象中未管理的其他形式的资源(才迫使这种事情发生。
请注意,这几乎是函数try/catch块的唯一合法使用。
不使用函数try块的更多原因。上面的代码被巧妙地破坏了。考虑一下:
class Cat
{
public:
Cat() {throw "oops";}
};
那么,在UseResources
的构造函数中会发生什么呢?很明显,表达式new Cat
会抛出。但这意味着cat
从未被初始化。这意味着delete cat
将产生未定义的行为。
您可以尝试通过使用复杂的lambda而不仅仅是new Cat
:来纠正这一问题
UseResources() try
: cat([]() -> Cat* try{ return new Cat;}catch(...) {return nullptr;} }())
, dog()
{ cout << "UseResources()" << endl; }
catch(...)
{
delete cat;
throw;
}
这在理论上解决了这个问题,但它打破了UseResources
的一个假定不变量。也就是说,UseResources::cat
在任何时候都将是有效的指针。如果这确实是UseResources
的不变量,那么此代码将失败,因为它允许构造UseResources
,尽管存在异常。
基本上,除非new Cat
是noexcept
(显式或隐式(,否则无法使此代码安全。
相比之下,这总是有效的:
class UseResources
{
unique_ptr<Cat> cat;
Dog dog;
public:
UseResources() : cat(new Cat), dog() { cout << "UseResources()" << endl; }
~UseResources() { cout << "~UseResources()" << endl; }
};
简而言之,将函数级try块视为严重的代码气味。
普通函数try块的用途相对较小。它们几乎与体内的试块完全相同:
int f1() try {
// body
} catch (Exc const & e) {
return -1;
}
int f2() {
try {
// body
} catch (Exc const & e) {
return -1;
}
}
唯一的区别是,函数try块位于稍大的函数范围内,而第二个构造位于函数体范围内——前者只看到函数参数,后者也看到局部变量(但这不会影响两个版本的try块(。
唯一有趣的应用程序是构造函数-try块:
Foo() try : a(1,2), b(), c(true) { /* ... */ } catch(...) { }
这是捕获其中一个初始化程序的异常的唯一方法。您不能处理异常,因为整个对象构造必须仍然失败(因此,无论您是否愿意,您都必须带着异常退出catch块(。然而,它是专门处理初始值设定项列表中异常的唯一方法。
这有用吗?可能不会 构造函数try块和以下更典型的"初始化为null并赋值"模式之间基本上没有区别,这本身就很糟糕:
Foo() : p1(NULL), p2(NULL), p3(NULL) {
p1 = new Bar;
try {
p2 = new Zip;
try {
p3 = new Gulp;
} catch (...) {
delete p2;
throw;
}
} catch(...) {
delete p1;
throw;
}
}
正如你所看到的,你有一个无法维持的,无法扩展的混乱。构造函数try块会更糟糕,因为您甚至无法判断已经分配了多少指针。因此,如果您正好有两个可泄漏的分配,那么只有才有用 更新:由于阅读了这个问题,我注意到实际上您根本无法使用catch块清理资源,因为引用成员对象是未定义的行为。所以【结束更新】
简而言之:没用。