上一篇文章詳細(xì)討論了C++的異常對象按值傳遞的方式,本文繼續(xù)討論另外的一種的方式:引用傳遞。
異常對象在什么時候構(gòu)造?
其實(shí)在上一篇文章中就已經(jīng)討論到了,假如異常對象按引用方式被傳遞,異常對象更應(yīng)該被構(gòu)造出一個臨時的變量。因此這里不再重復(fù)討論了。
異常對象按引用方式傳遞
引用是C++語言中引入的一種數(shù)據(jù)類型形式。它本質(zhì)上是一個指針,通過這個特殊的隱性指針來引用其它地方的一個變量。因此引用與指針有很多相似之處,但是引用用起來較指針更為安全,更為直觀和方便,所以C++語言建議C++程序員在編寫代碼中盡可能地多使用引用的方式來代替原來在C語言中使用指針的地方。這些地方主要是函數(shù)參數(shù)的定義上,另外還有就是catch到的異常對象的定義。
所以異常對象按引用方式傳遞,是不會發(fā)生對象的拷貝復(fù)制過程。這就導(dǎo)致引用方式要比傳值方式效率高,此時從拋出異常、捕獲異常再到異常錯誤處理結(jié)束過程中,總共只會發(fā)生兩次對象的構(gòu)造過程(一次是異常對象的初始化構(gòu)造過程,另一次就是當(dāng)執(zhí)行throw語句時所發(fā)生的臨時異常對象的拷貝復(fù)制的構(gòu)造過程)。而按值傳遞的方式總共是發(fā)生三次??纯词纠绦虬?!如下:
void main()
{
try
{
{
throw MyException();
}
}
// 注意:這里是定義了引用的方式
catch(MyException& e)
{
cout<<"捕獲到一個MyException類型的異常,名稱為:"< }
}
程序運(yùn)行的結(jié)果是:
構(gòu)造一個MyException異常對象,名稱為:none
拷貝一個MyException異常對象,名稱為:none
銷毀一個MyException異常對象,名稱為:none
捕獲到一個MyException類型的異常,名稱為:none
銷毀一個MyException異常對象,名稱為:none
程序的運(yùn)行結(jié)果是不是顯示出:異常對象確實(shí)是只發(fā)生兩次構(gòu)造過程。并且在執(zhí)行catch block之前,局部變量的異常對象已經(jīng)被析構(gòu)銷毀了,而屬于臨時變量的異常對象則是在catch block執(zhí)行錯誤處理完畢后才銷毀的。
那個被引用的臨時異常對象究竟身在何處?
呵呵!這還用問嗎,臨時異常對象當(dāng)然是在棧中。是的沒錯,就像發(fā)生函數(shù)調(diào)用時,與引用類型的參數(shù)傳遞一樣,它也是引用棧中的某塊區(qū)域的一個變量。但請大家提高警惕的是,這兩處有著非常大的不同,其實(shí)在一開始討論異常對象如何傳遞時就提到過,函數(shù)調(diào)用的過程是有序的的壓棧過程,請回顧一下《第9集 C++的異常對象如何傳送》中函數(shù)的調(diào)用過程與“?!蹦且还?jié)的內(nèi)容。棧是從高往低的不斷延伸擴(kuò)展,每發(fā)生一次函數(shù)調(diào)用時,棧中便添加了一塊格式非常整齊的函數(shù)幀區(qū)域(包含參數(shù)、返回地址和局部變量),當(dāng)前的函數(shù)通過ebp寄存器來尋址函數(shù)傳入的參數(shù)和函數(shù)內(nèi)部的局部變量。因此這樣對棧中的數(shù)據(jù)存儲是非常安全的,依照函數(shù)的調(diào)用次序(call stack),在棧中都有的一個對應(yīng)的函數(shù)幀一層層地從上往下整齊排列,當(dāng)一個函數(shù)執(zhí)行完畢,那么最低層的函數(shù)幀清除(該函數(shù)作用域內(nèi)的局部變量都析構(gòu)銷毀了),返回到上一層,如此不斷有序地進(jìn)行函數(shù)的調(diào)用與返回。
但發(fā)生異常時的情況呢?它的異常對象傳遞卻并沒有這么簡單,它需要在棧中把異常對象往上傳送,而且可能還要跳躍多個函數(shù)幀塊完成傳送,所以這就復(fù)雜了很多,當(dāng)然即便如此,只要我們找到了源對象數(shù)據(jù)塊和目標(biāo)對象數(shù)據(jù)塊,也能很方便地完成異常對象的數(shù)據(jù)的復(fù)制。但現(xiàn)在最棘手的問題是,如果采用引用傳遞的方式將會有很大的麻煩,為什么?試想!前面多次提到的臨時異常對象是在那里構(gòu)造的?對象數(shù)據(jù)又保存在什么地方?毫無疑問,對象數(shù)據(jù)肯定是在當(dāng)前(throw 異常的函數(shù))的那個函數(shù)幀區(qū)域,這是處于棧的最低部,現(xiàn)在假使匹配到的catch block是在上層(或更上層)的函數(shù)中,那么將會導(dǎo)致出現(xiàn)一種現(xiàn)象:就是在catch block的那個函數(shù)(執(zhí)行異常處理的模塊代碼中)會引用下面拋出異常的那個函數(shù)幀中的臨時異常對象。主人公阿愚現(xiàn)在終于恍然大悟了(不知閱讀到此處的C ++程序員朋友們現(xiàn)在領(lǐng)會了作者所說的意思沒有!如果還沒有,自己動手畫畫棧圖看看),是啊!確是如此,這太不安全了,按理說當(dāng)執(zhí)行到catch block中的代碼時,它下面的所有的函數(shù)幀(包括拋出異常的哪個函數(shù)幀)都將會無效,但此時卻引用到了下面的已經(jīng)失效了的函數(shù)幀中的臨時異常對象,雖說這個異常對象還沒有被析構(gòu),但完全有可能會發(fā)生覆蓋呀(棧是往下擴(kuò)展的)!
怎么辦!難道真的有可能會發(fā)生覆蓋嗎?那就太危險了。朋友們!放心吧!實(shí)際情況是絕對不會發(fā)生覆蓋的。為什么?哈哈!編譯器真是很聰明,它這里采用了一點(diǎn)點(diǎn)技巧,巧妙的避免的這個問題。下面用一個跨越了多個函數(shù)的異常的例子程序來詳細(xì)闡述之,如下:
void test2()
{
throw MyException();
}
void test()
{
test2();
}
void main()
{
try
{
test();
}
catch(MyException& e)
{
cout<<"捕獲到一個MyException類型的異常,名稱為:"< }
cout<<"那個臨時的異常對象應(yīng)該是在這之前析構(gòu)銷毀"< }
異常對象在什么時候構(gòu)造?
其實(shí)在上一篇文章中就已經(jīng)討論到了,假如異常對象按引用方式被傳遞,異常對象更應(yīng)該被構(gòu)造出一個臨時的變量。因此這里不再重復(fù)討論了。
異常對象按引用方式傳遞
引用是C++語言中引入的一種數(shù)據(jù)類型形式。它本質(zhì)上是一個指針,通過這個特殊的隱性指針來引用其它地方的一個變量。因此引用與指針有很多相似之處,但是引用用起來較指針更為安全,更為直觀和方便,所以C++語言建議C++程序員在編寫代碼中盡可能地多使用引用的方式來代替原來在C語言中使用指針的地方。這些地方主要是函數(shù)參數(shù)的定義上,另外還有就是catch到的異常對象的定義。
所以異常對象按引用方式傳遞,是不會發(fā)生對象的拷貝復(fù)制過程。這就導(dǎo)致引用方式要比傳值方式效率高,此時從拋出異常、捕獲異常再到異常錯誤處理結(jié)束過程中,總共只會發(fā)生兩次對象的構(gòu)造過程(一次是異常對象的初始化構(gòu)造過程,另一次就是當(dāng)執(zhí)行throw語句時所發(fā)生的臨時異常對象的拷貝復(fù)制的構(gòu)造過程)。而按值傳遞的方式總共是發(fā)生三次??纯词纠绦虬?!如下:
void main()
{
try
{
{
throw MyException();
}
}
// 注意:這里是定義了引用的方式
catch(MyException& e)
{
cout<<"捕獲到一個MyException類型的異常,名稱為:"<
}
程序運(yùn)行的結(jié)果是:
構(gòu)造一個MyException異常對象,名稱為:none
拷貝一個MyException異常對象,名稱為:none
銷毀一個MyException異常對象,名稱為:none
捕獲到一個MyException類型的異常,名稱為:none
銷毀一個MyException異常對象,名稱為:none
程序的運(yùn)行結(jié)果是不是顯示出:異常對象確實(shí)是只發(fā)生兩次構(gòu)造過程。并且在執(zhí)行catch block之前,局部變量的異常對象已經(jīng)被析構(gòu)銷毀了,而屬于臨時變量的異常對象則是在catch block執(zhí)行錯誤處理完畢后才銷毀的。
那個被引用的臨時異常對象究竟身在何處?
呵呵!這還用問嗎,臨時異常對象當(dāng)然是在棧中。是的沒錯,就像發(fā)生函數(shù)調(diào)用時,與引用類型的參數(shù)傳遞一樣,它也是引用棧中的某塊區(qū)域的一個變量。但請大家提高警惕的是,這兩處有著非常大的不同,其實(shí)在一開始討論異常對象如何傳遞時就提到過,函數(shù)調(diào)用的過程是有序的的壓棧過程,請回顧一下《第9集 C++的異常對象如何傳送》中函數(shù)的調(diào)用過程與“?!蹦且还?jié)的內(nèi)容。棧是從高往低的不斷延伸擴(kuò)展,每發(fā)生一次函數(shù)調(diào)用時,棧中便添加了一塊格式非常整齊的函數(shù)幀區(qū)域(包含參數(shù)、返回地址和局部變量),當(dāng)前的函數(shù)通過ebp寄存器來尋址函數(shù)傳入的參數(shù)和函數(shù)內(nèi)部的局部變量。因此這樣對棧中的數(shù)據(jù)存儲是非常安全的,依照函數(shù)的調(diào)用次序(call stack),在棧中都有的一個對應(yīng)的函數(shù)幀一層層地從上往下整齊排列,當(dāng)一個函數(shù)執(zhí)行完畢,那么最低層的函數(shù)幀清除(該函數(shù)作用域內(nèi)的局部變量都析構(gòu)銷毀了),返回到上一層,如此不斷有序地進(jìn)行函數(shù)的調(diào)用與返回。
但發(fā)生異常時的情況呢?它的異常對象傳遞卻并沒有這么簡單,它需要在棧中把異常對象往上傳送,而且可能還要跳躍多個函數(shù)幀塊完成傳送,所以這就復(fù)雜了很多,當(dāng)然即便如此,只要我們找到了源對象數(shù)據(jù)塊和目標(biāo)對象數(shù)據(jù)塊,也能很方便地完成異常對象的數(shù)據(jù)的復(fù)制。但現(xiàn)在最棘手的問題是,如果采用引用傳遞的方式將會有很大的麻煩,為什么?試想!前面多次提到的臨時異常對象是在那里構(gòu)造的?對象數(shù)據(jù)又保存在什么地方?毫無疑問,對象數(shù)據(jù)肯定是在當(dāng)前(throw 異常的函數(shù))的那個函數(shù)幀區(qū)域,這是處于棧的最低部,現(xiàn)在假使匹配到的catch block是在上層(或更上層)的函數(shù)中,那么將會導(dǎo)致出現(xiàn)一種現(xiàn)象:就是在catch block的那個函數(shù)(執(zhí)行異常處理的模塊代碼中)會引用下面拋出異常的那個函數(shù)幀中的臨時異常對象。主人公阿愚現(xiàn)在終于恍然大悟了(不知閱讀到此處的C ++程序員朋友們現(xiàn)在領(lǐng)會了作者所說的意思沒有!如果還沒有,自己動手畫畫棧圖看看),是啊!確是如此,這太不安全了,按理說當(dāng)執(zhí)行到catch block中的代碼時,它下面的所有的函數(shù)幀(包括拋出異常的哪個函數(shù)幀)都將會無效,但此時卻引用到了下面的已經(jīng)失效了的函數(shù)幀中的臨時異常對象,雖說這個異常對象還沒有被析構(gòu),但完全有可能會發(fā)生覆蓋呀(棧是往下擴(kuò)展的)!
怎么辦!難道真的有可能會發(fā)生覆蓋嗎?那就太危險了。朋友們!放心吧!實(shí)際情況是絕對不會發(fā)生覆蓋的。為什么?哈哈!編譯器真是很聰明,它這里采用了一點(diǎn)點(diǎn)技巧,巧妙的避免的這個問題。下面用一個跨越了多個函數(shù)的異常的例子程序來詳細(xì)闡述之,如下:
void test2()
{
throw MyException();
}
void test()
{
test2();
}
void main()
{
try
{
test();
}
catch(MyException& e)
{
cout<<"捕獲到一個MyException類型的異常,名稱為:"<
cout<<"那個臨時的異常對象應(yīng)該是在這之前析構(gòu)銷毀"<