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

字號:

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