条款9:利用destructors避免泄漏资源
这里开始介绍了auto_ptr,其实就是利用了c++局部对象在离开作用域的时候,其析构函数会被调用,来避免资源泄漏。这样的好处,就是不管是作用域正常结束(跑出代码块)还是异常结束(抛出异常),对象的析构函数都能保证被调用。
条款10:在constructors内阻止资源泄漏
c++只会析构已构造完成的对象。对象只有在其constructor执行完毕才算是完全构造妥当。
也就是说,c++不自动清理那些“构造期间抛出exceptions”的对象。所以你必须设计你的constructors,使它们在那种情况下亦能自我清理。
结论是:如果你以auto_ptr对象来取代pointer class members,你便对你的constructors做了强化工事,免除了“exceptions出现时发生资源泄漏”的危机,不再需要在destructors内亲自动手释放资源,并允许const member pointers得以和non-const member pointers有着一样优雅的处理方式
条款11:禁止异常流出destructors之外
析构函数的调用方式又两种情况:一种是当对象在正常情况下被销毁(离开了生命周期或明确的被删除);另一种是excpetion传播过程中的栈展开(stack-unwinding)机制。
如果控制权基于exception的因素离开dstructor,而此时正有另一个exception处于作用状态,c++会调用terminate函数
示例:
[cce lang="cpp"]
#include <iostream>
class A
{
public:
A() {}
~A() {}
};
int main()
{
try{
A a;
throw 0;
} catch (int e) {
std::cout << "catch exception: " << e << std::endl;
}
return 0;
}
[/cce]
这里A的析构函数没有抛出异常,执行结果是控制台输出了:catch exception: 0
[cce lang="cpp"]
#include <iostream>
class A
{
public:
A() {}
~A() {throw 1;}
};
int main()
{
try{
A a;
throw 0;
} catch (int e) {
std::cout << "catch exception: " << e << std::endl;
}
return 0;
}
[/cce]
这个例子里,A的析构函数抛出了一个异常,执行结果是悲剧的终止了:
terminate called after throwing an instance of 'int'
已放弃
因此,在无法判断析构函数是那种情况被调用的时候,千万不要抛出异常,否则程序会死的很惨。
判断当前是否在一个未被捕获的异常传播过程中,可以使用exception头文件中的uncaught_exception函数。
同样两个小例子:
[cce lang="cpp"]
#include <iostream>
#include <exception>
class A
{
public:
A() {}
~A() {
if(std::uncaught_exception()) {
std::cerr<< "have exception, cannot throw\n";
} else {
std::cout<< "have no exception, can throw\n";
throw 1;
}
}
};
int main()
{
try{
A a;
} catch (int e) {
std::cout << "catch exception: " << e << std::endl;
}
return 0;
}
[/cce]
这里调用过程中没有抛出异常,也就是说,A的析构函数是在实例a离开try代码块的时候调用的,uncaught_exception函数将会返回false,执行结果为:
have no exception, can throw
catch exception: 1
[cce lang="cpp"]
#include <iostream>
#include <exception>
class A
{
public:
A() {}
~A() {
if(std::uncaught_exception()) {
std::cerr<< "have exception, cannot throw\n";
} else {
std::cout<< "have no exception, can throw\n";
throw 1;
}
}
};
int main()
{
try{
A a;
throw 0;
} catch (int e) {
std::cout << "catch exception: " << e << std::endl;
}
return 0;
}
[/cce]
这里a的析构是因为在栈展开过程中,uncaught_exception会放回true,这个时候应该阻止析构函数继续抛出异常,否则c++会调用terminate函数结束程序。这里执行结果是:
have exception, cannot throw
catch exception: 0
当然这样写代码太痛苦了,所以直接避免析构函数抛出异常最好。
另一个系统函数不能抛出异常的原因,是异常抛出了之后,析构函数后面的代码将不会执行,可能会导致还有部分资源没有释放。