第一个例子:
void f()
{
FILE* __f;
__f=fopen(“filename”,”r+”);
if ( __f)
{
// file use ①
fclose(__f); //② 关闭文件
}
}
如果在//①file use过程中出现了错误,抛出了异常,程序就会异常回退,不会执行到②,文件也就没有关闭。解决这类问题可以使用异常处理:
void f()
{
FILE* __f;
__f=fopen(“filename”,”r+”);
if(__f)
{
try
{
// file use ①
}
catch(...)
{
fclose(__f); //② 关闭文件
throw; // 重新抛出异常
}
fclose(__f);//② 关闭文件
}
}
这种方案的缺点:
1.影响性能
2.背离异常设计的原则,异常处理代码和正常的代码没有很好的分离。只不过是:
if(error)
{
//错误处理
}
的偷懒写法。
在delphi 、java、 C#中引入了finally关键字,使这种设计方案更为流行;C#中的using只不过简化了try finally的写法。
更为合理的方案:
class file
{
FILE* __f;
Public:
file(char*filename,char* mode)
{
__f=fopen(filename,mode);
if (!__f) throw exception("open error");// 文件打开失败,抛出异常,表示类构造失败,这样在异常回退中就不会调用析构函数
}
~file()
{
fclose(__f); // 析构函数,关闭文件
}
operator FILE* ()
{
return __f;
}
};
void f()
{
file f1(“filename”,”r+”); //如果这边抛出异常,表示f1没有构造成功,就不会调用f1的析构
//①use file
};
如果f1构造成功,不管//①use file会不会出错,f1离开它的做用域时都将调用它的析构函数。
当然真正编码中,应该使用标准库中的ifstream和ofstream类。
第二个例子:
class A{
char* s;
int length;
public:
A(int n):length(n){
s=new char[n];
}
A(const A&);
A& operator=(const A& a){ //①
if(this!=&a){
delete[] s;
length=a.length;
s=new char[length]; //②
//...
}
}
~A(){
delete[] s;
}
};
在①赋值运算符重载中,如果②抛出异常(bad_alloc),已经delete[] s ,但是析构函数还会再一次delete[]s 。这当然不是我们想要的。重新设计①:
A& operator=(constA& a){
if(this!=&a){
char* p;
p=new char[length];
//...
length=a.length;
delete[] s; // delete 操作是不会抛异常的
s=p;
}
}