C++ 函数try块的用途是什么?
可能重复:C++ 函数try块的用途是什么?,c++,exception,exception-handling,function-try-block,C++,Exception,Exception Handling,Function Try Block,可能重复: 此代码在类UseResources中构造Dog对象时抛出int异常。int异常被正常的try catch块捕获,代码输出: Cat() Dog() ~Cat() Inside handler 输出是相同的 Cat() Dog() ~Cat() Inside handler i、 最终结果完全相同 那么,函数try块的用途是什么?想象一下,如果用户资源是这样定义的: class UseResources { class Cat *cat;
此代码在类
UseResources
中构造Dog
对象时抛出int
异常。int
异常被正常的try catch
块捕获,代码输出:
Cat()
Dog()
~Cat()
Inside handler
输出是相同的
Cat()
Dog()
~Cat()
Inside handler
i、 最终结果完全相同
那么,
函数try块的用途是什么?想象一下,如果用户资源
是这样定义的:
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; }
};
为了更全面地回答您的问题,构造函数中函数级try/catch块的目的就是专门进行这种清理。功能级别的try/catch块不能吞咽异常(常规块可以)。如果他们捕获了某个内容,当他们到达catch块的末尾时,他们将再次抛出该内容,除非您使用throw
显式地重新抛出该内容。您可以将一种类型的异常转换为另一种类型的异常,但您不能简单地接受它并像没有发生一样继续进行
这就是为什么应该使用值和智能指针而不是裸指针(即使作为类成员)的另一个原因。因为,就像你的例子一样,如果你只有成员值而不是指针,你就不必这么做。使用裸指针(或RAII对象中未管理的其他形式的资源)强制执行此类操作
请注意,这几乎是函数try/catch块的唯一合法使用
不使用函数try块的更多原因。上述代码被巧妙地破坏了。考虑这一点:
class Cat
{
public:
Cat() {throw "oops";}
};
那么,UseResources
的构造函数中会发生什么呢?显然,表达式newcat
将抛出。但这意味着,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;
}
简言之,将函数级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块来清理资源,因为引用成员对象是未定义的行为。So[结束更新]
简而言之:它是无用的。复制的复制的复制的复制的…这是c++11吗?我从没见过before@VJovic我不知道这种语言是什么时候引入的。但它不是新的。@VJovic它是C++03。几乎没用过。这就是你要找的,@Xeo。可能的副本请参见。有趣。到目前为止,我所看到的关于函数try的示例中没有一个会阻止这种清理功能。但是在你的例子中,程序无论如何都会中止,那么这个特性有什么大不了的呢?如果操作系统真的要这样做,清理的目的是什么?异常处理并不是解决故障,而是更优雅地处理故障。此功能可帮助您优雅地释放资源。一个实际的例子可能是一个客户端崩溃,并使用它来释放服务器资源,这样它就不会让其他客户端无法操作。@AJG85但是terminate()
将始终使用函数try块调用,在这种情况下,操作系统将释放任何资源。这不是真的吗?@user1042389:不,不会的<代码>捕获
仅在异常是功能级别的情况下自动重新调用异常。函数中的捕获
可以选择是否重新引发异常。如果没有,则在catch
块结束后继续执行。@nicolas我认为您的代码不安全:如果new Cat
抛出异常,则catch块也将执行。在这里,您对包含垃圾的指针调用delete。这个指针可能包含一个有效的内存地址,这将导致坏的程序崩溃。“它是无用的。”当然,除非你想转换异常。
class UseResources
{
unique_ptr<Cat> cat;
Dog dog;
public:
UseResources() : cat(new Cat), dog() { cout << "UseResources()" << endl; }
~UseResources() { cout << "~UseResources()" << endl; }
};
int f1() try {
// body
} catch (Exc const & e) {
return -1;
}
int f2() {
try {
// body
} catch (Exc const & e) {
return -1;
}
}
Foo() try : a(1,2), b(), c(true) { /* ... */ } catch(...) { }
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;
}
}