前段时间生病了,在家瘫了几天看张大仙,结果大仙直播间被斗鱼封了,哭唧唧,听不到大仙说骚话,我很难受,所以今天回来挖坑。
熟悉 c++ 的朋友们应该都知道,当我们抛出一个异常后,如果这个函数没能力去处理异常就会把这个异常原封不动的丢给调用这个函数的函数。一般来说,为了防止程序崩溃,我们会将一些敏感操作包裹到 try{}catch{} 中捕获异常并处理。常见的有越界啊,资源不够啊等等。。。
有没有想过,如果我使用了别人的库,但是我根本不知道他的库会抛异常,然后我就没去做处理,这个异常一直往上层函数抛,最后到了main函数也没能力处理这个异常,那会发生什么?
会发生什么自己动手试一试不就知道了,反正电脑又不会爆炸。
写一个简单的测试程序试一下。
#include<iostream>
using namespace std;
class Test
{
public:
Test()
{
cout<<"Test();"<<endl;
}
~Test()
{
cout<<"~Test();"<<endl;
}
};
int main()
{
static Test t;
throw 1;
return 0;
}
然后在我的 linux 中跑一下
很明显,第一行的 “Test();”是我们的构造函数中打印出来的,从运行结果看好像并没有调用我们的析构函数。
后面两行哪里打印出来的?我没有写这些打印语句啊???
其实认真分析后就知道了,肯定有个函数叫做 terminate 的打印了语句。好像还调用了 abort() 函数。
其实是这样的:
– 当main函数也无法处理异常时,terminate() 函数会自动调用。
– 默认情况下,terminate() 会调用库函数 abort() 终止程序。
– abort() 函数使得程序执行异常而立即退出。
– C++ 支持替换默认的 terminate() 函数实现。
好的,既然 terminate() 函数可以被替换,那我们试试吧。
替换 terminate() 函数,我们需要做到一下几点:
– 自定义一个无返回值无参数的函数
1、这个函数不能抛异常
2、这个函数必须以某种方式结束当前程序
– 调用 set_terminate() 设置自定义的结束函数
1、参数类型为 void(*)()
2、返回值为默认的 terminate() 函数入口地址
代码写一写。
#include<iostream>
#include<exception>
#include<cstdlib>
using namespace std;
class Test
{
public:
Test()
{
cout<<"Test();"<<endl;
}
~Test()
{
cout<<"~Test();"<<endl;
}
};
void lzy_terminate()
{
cout<<"lzy_terminate()"<<endl;
exit(1);
}
int main()
{
set_terminate(lzy_terminate);
static Test t;
throw 1;
return 0;
}
还是在我的 linux 里跑一下。
从输出结果可以看出,确实是运行了我们自定义的 terminate 函数。至于这里为什么会调用析构函数呢?那是因为我们调用的是 exit() 函数,执行时会释放程序的全局对象和静态局部对象。
如果我们不是调用exit 而是去调用 abort 会怎么样?
#include<iostream>
#include<exception>
#include<cstdlib>
using namespace std;
class Test
{
public:
Test()
{
cout<<"Test();"<<endl;
}
~Test()
{
cout<<"~Test();"<<endl;
}
};
void lzy_terminate()
{
cout<<"lzy_terminate()"<<endl;
//exit(1);
abort();
}
int main()
{
set_terminate(lzy_terminate);
static Test t;
throw 1;
return 0;
}
运行结果
从结果知道,abort() 并没有释放对象 t ,而是强制结束当前程序。第三行输出其实是 abort() 打印出来的。
如果异常在类的析构函数中抛出会怎么样?
我知道这样做是不对的,但是难免有些不懂的朋友可能会在析构函数中抛异常,现在就当作我不知道吧,我就是在析构中抛了异常,会怎么样?
资源会被重复释放,如果我们自己在自定义的 teminate 函数中做了释放资源的处理的话,岂不是很糟糕。
#include<iostream>
#include<exception>
#include<cstdlib>
using namespace std;
class Test
{
public:
Test()
{
cout<<"Test();"<<endl;
}
~Test()
{
cout<<"~Test();"<<endl;
throw 1;
}
};
void lzy_terminate()
{
cout<<"lzy_terminate()"<<endl;
exit(1);
}
int main()
{
set_terminate(lzy_terminate);
static Test t;
throw 1;
return 0;
}
在我的 linux 中运行的结果
好像没什么太多关系嘛。
不!!!大错特错,我们并不能保证在任何平台下都能像 linux 中那样吗?
同样的程序,在Qt 中 MinGW 32位编译器编译运行一下,结果是怎样的?
很明显,terminate() 被调用了两次。这样是不是很危险啊,很可能出现重复释放资源的情况。
为什么 terminate() 在设计的时候采用 abort() 来结束程序,就是怕析构函数又抛出异常。
所以啊,不要再析构函数里抛出异常,析构函数是用来释放资源的,抛异常可能导致资源不能正确释放,同时可能导致全局的结束函数 terminate() 被重复调用,很可能让系统进入不稳定的状态。
如果异常没有被处理,最后terminate()会结束整个程序。
terminate()是整个程序释放系统资源的最后机会。
结束函数可以自定义,但不能继续抛出异常。
析构函数中不能抛出异常,这可能导致terminate()多次调用。