第10集C++的異常對(duì)象按傳值的方式被復(fù)制和傳遞

字號(hào):

上一篇文章中對(duì)C++的異常對(duì)象如何被傳遞做了一個(gè)概要性的介紹,其中得知C++的異常對(duì)象的傳遞方式有指針?lè)绞?、傳值方式和引用方式三種?,F(xiàn)在開(kāi)始討論最簡(jiǎn)單的一種傳遞的方式:按值傳遞。
    異常對(duì)象在什么時(shí)候構(gòu)造?
    1、按傳值的方式傳遞異常對(duì)象時(shí),被拋出的異常都是局部變量,而且是臨時(shí)的局部變量。什么是臨時(shí)的局部變量,這大家可能都知道,例如發(fā)生函數(shù)調(diào)用時(shí),按值傳遞的參數(shù)就會(huì)被臨時(shí)復(fù)制一份,這就是臨時(shí)局部變量,一般臨時(shí)局部變量轉(zhuǎn)瞬即逝。
    主人公阿愚對(duì)這開(kāi)始有點(diǎn)不太相信。不會(huì)吧,誰(shuí)說(shuō)異常對(duì)象都是臨時(shí)的局部變量,應(yīng)該是普通的局部變量,甚至是全局性變量,而且還可以是堆中動(dòng)態(tài)分配的異常變量。是的,這上面說(shuō)的好象沒(méi)錯(cuò),但是實(shí)際真實(shí)發(fā)生的情況是,每當(dāng)在throw語(yǔ)句拋出一個(gè)異常時(shí),不管你原來(lái)構(gòu)造的對(duì)象是什么性質(zhì)的變量,此時(shí)它都會(huì)復(fù)制一份臨時(shí)局部變量,還是具體看看例程吧!如下:
    class MyException
    {
    public:
    MyException (string name="none") : m_name(name)
    {
    cout << "構(gòu)造一個(gè)MyException異常對(duì)象,名稱為:"<    }
    MyException (const MyException& old_e)
    {
    m_name = old_e.m_name;
    cout << "拷貝一個(gè)MyException異常對(duì)象,名稱為:"<    }
    operator= (const MyException& old_e)
    {
    m_name = old_e.m_name;
    cout << "賦值拷貝一個(gè)MyException異常對(duì)象,名稱為:"<    }
    virtual ~ MyException ()
    {
    cout << "銷毀一個(gè)MyException異常對(duì)象,名稱為:" <    }
    string GetName() {return m_name;}
    protected:
    string m_name;
    };
    void main()
    {
    try
    {
    {
    // 構(gòu)造一個(gè)異常對(duì)象,這是局部變量
    MyException ex_obj1("ex_obj1");
    // 這里拋出異常對(duì)象
    // 注意這時(shí)VC編譯器會(huì)復(fù)制一份新的異常對(duì)象,臨時(shí)變量
    throw ex_obj1;
    }
    }
    catch(...)
    {
    cout<<"catch unknow exception"<    }
    }
    程序運(yùn)行的結(jié)果是:
    構(gòu)造一個(gè)MyException異常對(duì)象,名稱為:ex_obj1
    拷貝一個(gè)MyException異常對(duì)象,名稱為:ex_obj1
    銷毀一個(gè)MyException異常對(duì)象,名稱為:ex_obj1
    catch unknow exception
    銷毀一個(gè)MyException異常對(duì)象,名稱為:ex_obj1
    瞧見(jiàn)了吧,異常對(duì)象確實(shí)是被復(fù)制了一份,如果還不相信那份異常對(duì)象是在throw ex_obj1這條語(yǔ)句執(zhí)行時(shí)被復(fù)制的,你可以在VC環(huán)境中調(diào)試這個(gè)程序,再把這條語(yǔ)句反匯編出來(lái),你會(huì)發(fā)現(xiàn)這里確實(shí)插入了一段調(diào)用拷貝構(gòu)造函數(shù)的代碼。
    2、而且其它幾種拋出異常的方式也會(huì)有同樣的結(jié)果,都會(huì)構(gòu)造一份臨時(shí)局部變量。執(zhí)著的阿愚可是每種情況都測(cè)試了一下,代碼如下:
    // 這是全局變量的異常對(duì)象
    // MyException ex_global_obj("ex_global_obj");
    void main()
    {
    try
    {
    {
    // 構(gòu)造一個(gè)異常對(duì)象,這是局部變量
    MyException ex_obj1("ex_obj1");
    throw ex_obj1;
    // 這種也是臨時(shí)變量
    // 這種方式是最常見(jiàn)拋出異常的方式
    //throw MyException("ex_obj2");
    // 這種異常對(duì)象原來(lái)是在堆中構(gòu)造的
    // 但這里也會(huì)復(fù)制一份新的異常對(duì)象
    // 注意:這里有資源泄漏呦!
    //throw *(new MyException("ex_obj2"));
    // 全局變量
    // 同樣這里也會(huì)復(fù)制一份新的異常對(duì)象
    //throw ex_global_obj;
    }
    }
    catch(...)
    {
    cout<<"catch unknow exception"<    }
    大家也可以對(duì)每種情況都試一試,注意是不是確實(shí)無(wú)論哪種情況都會(huì)復(fù)制一份本地的臨時(shí)變量了呢!
    另外請(qǐng)朋友們特別注意的是,這是VC編譯器這樣做的,其它的C++編譯器是不是也這樣的呢?也許不一定,不過(guò)很大可能都是采取這樣一種方式(阿愚沒(méi)有在其它每一種C++編譯器都做過(guò)測(cè)試,所以不敢妄下結(jié)論)。
    為什么要再?gòu)?fù)制一份臨時(shí)變量呢?是不是覺(jué)得有點(diǎn)多此一舉,不!朋友們,請(qǐng)仔細(xì)再想想,因?yàn)榧偃绮贿@樣做,不把異常對(duì)象復(fù)制一份臨時(shí)的局部變量出來(lái),那么是不是會(huì)導(dǎo)致一些問(wèn)題,或產(chǎn)生一些矛盾呢?的確如此!試想在拋出異常后,如果異常對(duì)象是局部變量,那么C++標(biāo)準(zhǔn)規(guī)定了無(wú)論在何種情況下,只要局部變量離開(kāi)其生存作用域,局部變量就必須要被銷毀,可現(xiàn)在如果作為局部變量的異常對(duì)象在控制進(jìn)入catch block之前,它就已經(jīng)被析構(gòu)銷毀了,那么問(wèn)題不就嚴(yán)重了嗎?因此它這里就復(fù)制了一份臨時(shí)變量,它可以在catch block內(nèi)的異常處理完畢以后再銷毀這個(gè)臨時(shí)的變量。
    主人公阿愚現(xiàn)在好像是逐漸得明白了,原來(lái)如此,但仔細(xì)一想,不對(duì)呀!上面描述的不準(zhǔn)確呀!難道不可以在離開(kāi)拋出異常的那個(gè)函數(shù)的作用域時(shí),先把異常對(duì)象拷貝復(fù)制到上層的catch block中,然后再析構(gòu)局部變量,最后才進(jìn)入到catch block里面執(zhí)行嗎!分析的非常的棒!阿愚終于有些系統(tǒng)分析員的頭腦了。是的,現(xiàn)在的VC編譯器就是按這種順序工作的。
    可那到底為什么要復(fù)制臨時(shí)變量呢?呵呵!要請(qǐng)教阿愚一個(gè)問(wèn)題,如果catch后面的是采用引用傳遞異常對(duì)象的方式,也即沒(méi)有拷貝復(fù)制這一過(guò)程,那么怎辦?那個(gè)引用指向誰(shuí)呀,指向一個(gè)已經(jīng)析構(gòu)了的異常對(duì)象?。偛恢劣谡f(shuō),等執(zhí)行完catch block之后,再來(lái)析構(gòu)原來(lái)屬于局部變量的異常對(duì)象,這也太荒唐了)。所以嗎?才如此。