一直都是个C++菜鸟,每次写程序都情不自禁地往C上靠,因此很少使用到C++的一些特性。这几天封装一个异常处理模块,开始在程序中使用throw、try、catch,发现这玩意儿确实不错,不过有很多地方需要考虑。昨晚在VC里调试了下,得出了throw之后一些对象的析构顺序。
下面提到的对象不单纯局限于类的实例,还包括基本类型变量和申请的资源,而析构不单纯局限与类实例的资源释放。只是本文中以类的实例为示例做说明。
throw的时候将被抛出的对象拷贝一份到专用的异常栈上,接着按声明时相反的顺序先析构掉本层作用域内的对象,再往上析构掉高层作用域中的对象,依次上推直到将throw之前的,在本函数中声明的所有对象都析构掉为止。然后再执行catch中的相关语句,最后才析构掉拷贝对象。
下面的代码用来说明对象析构顺序:
#include <iostream>
#include <string.h>
using namespace std;
#define _MAX_STRING 256
/* 测试析构顺序用的测试异常类 */
class CTestException
{
public:
CTestException(int i, const char * pszScope)
{
__m_iNum = i;
strcpy(__m_szScope, pszScope);
cout << "(^_^) 我来也,我是第" << __m_iNum << "个对象,我生在"
<< __m_szScope << "里!" << endl;
}
~CTestException(void)
{
cout << "⊙^⊙ 永别了,我是第" << __m_iNum << "个对象,我死在"
<< __m_szScope << "中!" << endl;
}
void speak(void)
{
cout << ">^o^< 我活着,我是第" << __m_iNum << "个对象!" << endl;
}
private:
int __m_iNum;
char __m_szScope[_MAX_STRING];
};
/* 抛出测试异常对象的异常测试函数 */
int ExceptionTest(int i)
{
char szScope[] = "ExceptionTest";
CTestException objTE1(1, szScope);
CTestException objTE2(2, szScope);
CTestException objTE3(3, szScope);
switch (i)
{
/*当 0<=i<= 3,switch之后的语句不被执行,obj4、obj5不被声明,函数异常返回 */
case 1: throw(objTE1); break;
case 2: throw(objTE2); break;
case 3: throw(objTE3); break;
/* 当i<0或i>3,后面的语句被执行,obj4、obj5被声明并最终被析构,函数正常返回*/
default: break;
}
CTestException objTE4(4, szScope);
CTestException objTE5(5, szScope);
return i;
}
int main(void)
{
CTestException objTE6(6, "main");
CTestException objTE7(7, "main");
try
{
int i = 0;
CTestException objTE8(8, "try");
CTestException objTE9(9, "try");
cout << endl << "ExceptionText中谁是异常分子:";
cin >> i;
cout << "第" << i << "个对象被指认为异常分子!" << endl << endl;
cout << ExceptionTest(i);
CTestException objTE10(10, "main");
}
catch(CTestException &objTE)
{
objTE.speak();
}
CTestException objTE11(11, "main");
return 0;
}
运行时指定objTE2为异常对象,输入结果如下:
(^_^) 我来也,我是第6个对象,我生在main里!
(^_^) 我来也,我是第7个对象,我生在main里!
(^_^) 我来也,我是第8个对象,我生在try里!
(^_^) 我来也,我是第9个对象,我生在try里!
ExceptionText中谁是异常分子:2
第2个对象被指认为异常分子!
(^_^) 我来也,我是第1个对象,我生在ExceptionTest里!
(^_^) 我来也,我是第2个对象,我生在ExceptionTest里!
(^_^) 我来也,我是第3个对象,我生在ExceptionTest里!
⊙^⊙ 永别了,我是第3个对象,我死在ExceptionTest中!
⊙^⊙ 永别了,我是第2个对象,我死在ExceptionTest中!
⊙^⊙ 永别了,我是第1个对象,我死在ExceptionTest中!
⊙^⊙ 永别了,我是第9个对象,我死在try中!
⊙^⊙ 永别了,我是第8个对象,我死在try中!
>^o^< 我活着,我是第2个对象!
⊙^⊙ 永别了,我是第2个对象,我死在ExceptionTest中!
(^_^) 我来也,我是第11个对象,我生在main里!
⊙^⊙ 永别了,我是第11个对象,我死在main中!
⊙^⊙ 永别了,我是第7个对象,我死在main中!
⊙^⊙ 永别了,我是第6个对象,我死在main中!
从上面的输出结果可以得出:
1、先析构掉抛出异常的ExceptionTest函数中的对象,再析构掉try中的对象,最后析构掉main函数中的对象。(在main函数中,try、catch组合之外,被析构掉的对象属于正常析构,与异常无关。)<由析构输出的作用域范围可得知>
2、在函数正常返回之前就被析构掉的对象都是声明在抛出异常的语句之前的。<由抛出异常语句之前的对象先被产生析构输出可得知>
3、对象的析构顺序和原先的构造顺序刚好是相反的,不过这与异常无关,对象在正常情况下也是这种顺序。<由所有构造输出与析构输出的对象编号对应关系可得知>
4、在ExceptionTest函数中,异常抛出语句下面的语句不再被执行。<由objTE4和objTE5没有被构造可得知>
5、在try中,调用ExcepionTest函数抛出异常,下面的语句不再被执行,甚至ExceptionTest函数都没有正常返回。<由objTE10对象没有被构造,前一语句没有输出函数返回值可得知>
6、在异常对象被析构之前,将其拷贝一个副本到专用的析构栈中,以便在catch中进行调用。<在objTE2对象产生了析构输出之后还能调用它的speak函数成员可得知>
6、在main中,try、catch组合之外,处理完异常后,下面的语句继续被执行。<由objTE11对象的构造输出可得知>