第14集再探C++中異常的rethrow

字號(hào):

在相遇篇中的《第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所示。