在相遇篇中的《第5集 C++的異常rethrow》文章中,已經(jīng)比較詳細(xì)討論了異常重新被拋出的處理過程。但是有一點(diǎn)卻并沒有敘述到,那就是C++異常重新被拋出時(shí)(rethrow),異常對(duì)象的構(gòu)造、傳遞和析構(gòu)銷毀的過程會(huì)有哪些變化和不同之處。為了精益求精,力求對(duì)每一個(gè)細(xì)節(jié)都深入了解和掌握,下面再全面闡述一下各種不同組合情況下的異常構(gòu)造和析構(gòu)的過程。
大家現(xiàn)在知道,異常的重新被拋出有兩種方式。其一,由于當(dāng)前的catch block塊處理不了這個(gè)異常,所以這個(gè)異常對(duì)象再次原封不動(dòng)地被重新拋出;其二,就是在當(dāng)前的catch block塊處理異常時(shí),又激發(fā)了另外一個(gè)異常的拋出。另外,由于異常對(duì)象的傳遞方式有三種:傳值、傳引用和傳指針。所以實(shí)際上這就導(dǎo)致了有6種不同的組合情況。下面分別闡述之。
異常對(duì)象再次原封不動(dòng)地被重新拋出
1、首先討論異常對(duì)象“按值傳遞”的方式下,異常對(duì)象的構(gòu)造、傳遞和析構(gòu)銷毀的過程有何不同之處?毫無(wú)疑問,在異常被重新被拋出時(shí),前面的一個(gè)異常對(duì)象的構(gòu)造和傳遞過程肯定不會(huì)被影響,也即“按值傳遞”的方式下,異常被構(gòu)造了3次,異常對(duì)象被“按值傳遞”到這個(gè)catch block中。實(shí)際上,需要研究的是,當(dāng)異常被重新被拋出時(shí),這個(gè)異常對(duì)象是否在離開當(dāng)前的這個(gè)catch block域時(shí)會(huì)析構(gòu)銷毀掉,并且這個(gè)異常對(duì)象是否還會(huì)再次被復(fù)制構(gòu)造?以及重新被拋出的異常對(duì)象按什么方式被傳遞?看如下例程:
class MyException
{
public:
MyException (string name="none") : m_name(name)
{
number = ++count;
cout << "構(gòu)造一個(gè)MyException異常對(duì)象,名稱為:"< }
MyException (const MyException& old_e)
{
m_name = old_e.m_name;
number = ++count;
cout << "拷貝一個(gè)MyException異常對(duì)象,名稱為:"< }
operator= (const MyException& old_e)
{
m_name = old_e.m_name;
number = ++count;
cout << "賦值拷貝一個(gè)MyException異常對(duì)象,名稱為:"< }
virtual ~ MyException ()
{
cout << "銷毀一個(gè)MyException異常對(duì)象,名稱為:" < }
string GetName()
{
char tmp[20];
memset(tmp, 0, sizeof(tmp));
sprintf(tmp, "%s:%d", m_name.c_str(), number);
return tmp;
}
virtual string Test_Virtual_Func() { return "這是MyException類型的異常對(duì)象";}
protected:
string m_name;
int number;
static int count;
};
int MyException::count = 0;
void main()
{
try
{
try
{
// 拋出一個(gè)異常對(duì)象
throw MyException("ex_obj1");
}
// 異常對(duì)象按值傳遞
catch(MyException e)
{
cout< cout<<"下面重新拋出異常"< // 異常對(duì)象重新被拋出
throw;
}
}
// 異常對(duì)象再次按值傳遞
catch(MyException e)
{
cout< }
}
程序運(yùn)行的結(jié)果是:
構(gòu)造一個(gè)MyException異常對(duì)象,名稱為:ex_obj1:1
拷貝一個(gè)MyException異常對(duì)象,名稱為:ex_obj1:2
拷貝一個(gè)MyException異常對(duì)象,名稱為:ex_obj1:3
銷毀一個(gè)MyException異常對(duì)象,名稱為:ex_obj1:1
捕獲到一個(gè)MyException*類型的異常,名稱為:ex_obj1:3
下面重新拋出異常
拷貝一個(gè)MyException異常對(duì)象,名稱為:ex_obj1:4
銷毀一個(gè)MyException異常對(duì)象,名稱為:ex_obj1:3
捕獲到一個(gè)MyException*類型的異常,名稱為:ex_obj1:4
銷毀一個(gè)MyException異常對(duì)象,名稱為:ex_obj1:4
銷毀一個(gè)MyException異常對(duì)象,名稱為:ex_obj1:2
通過上面的程序運(yùn)行結(jié)果,可以很明顯地看出,異常對(duì)象在被重新拋出時(shí),又有了一次拷貝復(fù)制的過程,瞧瞧!正常情況下,按值傳遞異常的方式應(yīng)該是有3次構(gòu)造對(duì)象的過程,可現(xiàn)在有了4次。那么這個(gè)異常對(duì)象在什么時(shí)候又再次被復(fù)制構(gòu)造的呢?仔細(xì)分析一下,其實(shí)也不難明白, “異常對(duì)象ex_obj1:1”是局部變量;“異常對(duì)象ex_obj1:2”是臨時(shí)變量;“異常對(duì)象ex_obj1:3”是第一個(gè)(內(nèi)層的)catch block中的參數(shù)變量。當(dāng)在catch block中再次throw異常對(duì)象時(shí),它會(huì)即刻準(zhǔn)備離開當(dāng)前的catch block域,繼續(xù)往上搜索對(duì)應(yīng)的catch block模塊,找到后,即完成異常對(duì)象的又一次復(fù)制構(gòu)造過程,也即把異常對(duì)象傳遞給上一層的catch block域中。之后,正式離開內(nèi)層的catch block域,并析構(gòu)銷毀這個(gè)catch block域中的異常對(duì)象ex_obj1:3,注意此時(shí),屬于臨時(shí)變量形式的異常對(duì)象ex_obj1:2并沒有被析構(gòu),而是直至到后一個(gè)catch block處理完后,先析構(gòu)銷毀異常對(duì)象ex_obj1:4,再才銷毀異常對(duì)象ex_obj1:2。整個(gè)程序的執(zhí)行流程如圖14-1所示。
大家現(xiàn)在知道,異常的重新被拋出有兩種方式。其一,由于當(dāng)前的catch block塊處理不了這個(gè)異常,所以這個(gè)異常對(duì)象再次原封不動(dòng)地被重新拋出;其二,就是在當(dāng)前的catch block塊處理異常時(shí),又激發(fā)了另外一個(gè)異常的拋出。另外,由于異常對(duì)象的傳遞方式有三種:傳值、傳引用和傳指針。所以實(shí)際上這就導(dǎo)致了有6種不同的組合情況。下面分別闡述之。
異常對(duì)象再次原封不動(dòng)地被重新拋出
1、首先討論異常對(duì)象“按值傳遞”的方式下,異常對(duì)象的構(gòu)造、傳遞和析構(gòu)銷毀的過程有何不同之處?毫無(wú)疑問,在異常被重新被拋出時(shí),前面的一個(gè)異常對(duì)象的構(gòu)造和傳遞過程肯定不會(huì)被影響,也即“按值傳遞”的方式下,異常被構(gòu)造了3次,異常對(duì)象被“按值傳遞”到這個(gè)catch block中。實(shí)際上,需要研究的是,當(dāng)異常被重新被拋出時(shí),這個(gè)異常對(duì)象是否在離開當(dāng)前的這個(gè)catch block域時(shí)會(huì)析構(gòu)銷毀掉,并且這個(gè)異常對(duì)象是否還會(huì)再次被復(fù)制構(gòu)造?以及重新被拋出的異常對(duì)象按什么方式被傳遞?看如下例程:
class MyException
{
public:
MyException (string name="none") : m_name(name)
{
number = ++count;
cout << "構(gòu)造一個(gè)MyException異常對(duì)象,名稱為:"<
MyException (const MyException& old_e)
{
m_name = old_e.m_name;
number = ++count;
cout << "拷貝一個(gè)MyException異常對(duì)象,名稱為:"<
operator= (const MyException& old_e)
{
m_name = old_e.m_name;
number = ++count;
cout << "賦值拷貝一個(gè)MyException異常對(duì)象,名稱為:"<
virtual ~ MyException ()
{
cout << "銷毀一個(gè)MyException異常對(duì)象,名稱為:" <
string GetName()
{
char tmp[20];
memset(tmp, 0, sizeof(tmp));
sprintf(tmp, "%s:%d", m_name.c_str(), number);
return tmp;
}
virtual string Test_Virtual_Func() { return "這是MyException類型的異常對(duì)象";}
protected:
string m_name;
int number;
static int count;
};
int MyException::count = 0;
void main()
{
try
{
try
{
// 拋出一個(gè)異常對(duì)象
throw MyException("ex_obj1");
}
// 異常對(duì)象按值傳遞
catch(MyException e)
{
cout<
throw;
}
}
// 異常對(duì)象再次按值傳遞
catch(MyException e)
{
cout<
}
程序運(yùn)行的結(jié)果是:
構(gòu)造一個(gè)MyException異常對(duì)象,名稱為:ex_obj1:1
拷貝一個(gè)MyException異常對(duì)象,名稱為:ex_obj1:2
拷貝一個(gè)MyException異常對(duì)象,名稱為:ex_obj1:3
銷毀一個(gè)MyException異常對(duì)象,名稱為:ex_obj1:1
捕獲到一個(gè)MyException*類型的異常,名稱為:ex_obj1:3
下面重新拋出異常
拷貝一個(gè)MyException異常對(duì)象,名稱為:ex_obj1:4
銷毀一個(gè)MyException異常對(duì)象,名稱為:ex_obj1:3
捕獲到一個(gè)MyException*類型的異常,名稱為:ex_obj1:4
銷毀一個(gè)MyException異常對(duì)象,名稱為:ex_obj1:4
銷毀一個(gè)MyException異常對(duì)象,名稱為:ex_obj1:2
通過上面的程序運(yùn)行結(jié)果,可以很明顯地看出,異常對(duì)象在被重新拋出時(shí),又有了一次拷貝復(fù)制的過程,瞧瞧!正常情況下,按值傳遞異常的方式應(yīng)該是有3次構(gòu)造對(duì)象的過程,可現(xiàn)在有了4次。那么這個(gè)異常對(duì)象在什么時(shí)候又再次被復(fù)制構(gòu)造的呢?仔細(xì)分析一下,其實(shí)也不難明白, “異常對(duì)象ex_obj1:1”是局部變量;“異常對(duì)象ex_obj1:2”是臨時(shí)變量;“異常對(duì)象ex_obj1:3”是第一個(gè)(內(nèi)層的)catch block中的參數(shù)變量。當(dāng)在catch block中再次throw異常對(duì)象時(shí),它會(huì)即刻準(zhǔn)備離開當(dāng)前的catch block域,繼續(xù)往上搜索對(duì)應(yīng)的catch block模塊,找到后,即完成異常對(duì)象的又一次復(fù)制構(gòu)造過程,也即把異常對(duì)象傳遞給上一層的catch block域中。之后,正式離開內(nèi)層的catch block域,并析構(gòu)銷毀這個(gè)catch block域中的異常對(duì)象ex_obj1:3,注意此時(shí),屬于臨時(shí)變量形式的異常對(duì)象ex_obj1:2并沒有被析構(gòu),而是直至到后一個(gè)catch block處理完后,先析構(gòu)銷毀異常對(duì)象ex_obj1:4,再才銷毀異常對(duì)象ex_obj1:2。整個(gè)程序的執(zhí)行流程如圖14-1所示。