• <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>

            Design&Art

            C++博客 首頁 新隨筆 聯(lián)系 聚合 管理
              26 Posts :: 0 Stories :: 38 Comments :: 0 Trackbacks
            c++中的錯誤處理
            原創(chuàng):monkeyfu 2003年6月19日

            處理在程序的運行時刻發(fā)生的錯誤,對于任何一個程序設(shè)計者來講都是不陌生的。對于錯誤的處理,我們有很多方法,本篇著重介紹的是C++中的錯誤異常處理。

            在介紹C++中的錯誤異常處理之前,我們先來看一下常用的錯誤處理方式。

            1.返回值 可以說這是最常用的錯誤處理方式之一,但其存在著一個致命的問題。就是返回值的檢查與否是由調(diào)用者主動控制的。如果調(diào)用者不檢查返回值,那也沒有任何 機(jī)制能夠強(qiáng)迫他這么做。再一個,考慮在C++中參數(shù)表相同而返回值不同的重載情況。在這種情況下,如果調(diào)用者不檢查返回值的話,編譯器根本不清楚應(yīng)該調(diào)用哪個函數(shù)。
            2.全局狀態(tài)標(biāo)示符 這種辦法同返回值一樣,也是需要調(diào)用者主動檢查的。并且由于其是全局的,因此在多線程程序中,還必須保證它的線程安全性,必須要讓檢查者知道這是誰的返回值。
            3.setjmp()/longjmp() 你完全可以將longjmp()當(dāng)成遠(yuǎn)程的goto語句進(jìn)行調(diào)用(goto語句只能左右于本地函數(shù)里)。但這個函數(shù)卻存在著很大甚至是致命的危險。暫且放下該函數(shù)會破壞結(jié)構(gòu)化程序設(shè)計風(fēng)格不說。其一,longjmp()只能處理int型的異常。其二,也就是最致命的一點就是,longjmp()不會調(diào)用析構(gòu)函數(shù),而C++的 異常處理機(jī)制卻會完成這個事情。因此,在C++中,千萬不要使用setjmp()longjmp()函數(shù)。
            4.斷言 對于斷言(Assert),其僅僅是在Debug版本中起作用,在Release中其是不存在的。另外斷言與我們通常所說的錯誤處理方式不同,他是用來處理我們可能會發(fā)生這個錯誤,并能夠避免的這種情況。

            在介紹過上面那些存在問題的錯誤處理方式后,現(xiàn)在讓我們來看看C++中的異常機(jī)制是如何處理錯誤的。首先說,C++的異常處理不會像上面提到的那些方法一樣,必須是調(diào)用著主動檢查。因為在C++中,一旦拋出(throw)一個異常,而程序不捕獲(catch)的話,那么最終的結(jié)果就是abort()函數(shù)被調(diào)用,使得程序被終止。

            下面我們來看一下C++異常處理(以下稱EH)的基本語法和語意。
            其引入了3個關(guān)鍵字,分別是:

            catch
            , throw, try

            throw

            異常由throw拋出,其格式為

            throw [expression]

            函數(shù)在定義時通過異常規(guī)格申明定義其會拋出什么類型的異常,其格式為:

            throw([type-ID-list])

            type-ID-list是一個可選項,其中包括了一個或多個類型的名字,它們之間以逗號分隔。

            例如:

            void func() throw(int, some_class_type)

            則表明會拋出intsome_class_type類型異常。

            對于一個空的異常規(guī)格申明,表示不拋出任何異常。
            如:

            void func() throw(...)

            而如果函數(shù)沒有異常規(guī)格申明,則表示會拋出任何類型的異常。
            不過這里存在一種情況,例如:

            void func() throw(int) //指明拋出int型異常
            {
            ...
            subfunc(); //但可能從這里拋出非int型異常
            ...
            }

            try -- catch

            try塊中的異常處理函數(shù)對異常進(jìn)行捕獲。其可以包含一個或多個處理函數(shù),其形式如下:

            catch (exception-declaration) compound-statement

            處理函數(shù)的異常申明指明了其要捕獲什么類型的異常。
            對于異常申明其可以是無名的,例如:catch(char *),其表明會捕獲一個char *類型異常,但由于是無名的,因此不能對其進(jìn)行操作。另外異常申明也可以存在如下形式:catch(...),其表明會捕獲任何類型的異常。

            舉例:

            void func() throw(int, some_class_type)
            {
                int i;
                ........
                throw i;
                ........
            }

            int main()
            {
                try
               
            {
                    func();
                }
                catch(int e)
                {
                    //處理int型異常
               
            }
                catch(some_class_type)
                {
                    //處理some_class_type型異常
               
            }
                .......
                return 0;
            }

            從上面的例子可以看出,當(dāng)函數(shù)拋出異常時,throw后面要帶一個拋出的對象。但這并不是必須的,例如:

            catch(int e)
            {
                .......
                throw;
            }

            throw后面沒有接任何對象,這表明throw會再次拋出已存在的異常對象,因此其必須位于catch塊中。

            下面介紹一些C++提供的標(biāo)準(zhǔn)異常

            namespace std
            {
            //exception派生
            class logic_error; //邏輯錯誤,在程序運行前可以檢測出來

            //logic_error派生
            class domain_error; //違反了前置條件
            class invalid_argument; //指出函數(shù)的一個無效參數(shù)
            class length_error; //指出有一個超過類型size_t的最大可表現(xiàn)值長度的對象的企圖
            class out_of_range; //參數(shù)越界
            class bad_cast; //在運行時類型識別中有一個無效的dynamic_cast表達(dá)式
            class bad_typeid; //報告在表達(dá)試typeid(*p)中有一個空指針p

            //exception派生
            class runtime_error; //運行時錯誤,僅在程序運行中檢測到

            //runtime_error派生
            class range_error; //違反后置條件
            class overflow_error; //報告一個算術(shù)溢出
            class bad_alloc; //存儲分配錯誤
            }

            在C++標(biāo)準(zhǔn)庫頭文件<exception>申明了幾個EH類型和函數(shù),它們是:

            namespace std
            {
            //EH類型
            class bad_exception;
            class exception;

            typedef void (*terminate_handler)();
            typedef void (*unexpected_handler)();

            // 函數(shù)
            terminate_handler set_terminate(terminate_handler) throw();
            unexpected_handler set_unexpected(unexpected_handler) throw();

            void terminate();
            void unexpected();

            bool uncaught_exception();
            }


            exception 是所有標(biāo)準(zhǔn)庫拋出的異常的基類。
            uncaught_exception() 函數(shù)在異常被拋出卻沒有被捕獲時返回true,其它情況返回false
            terminate() 在異常處理陷入了不可恢復(fù)狀態(tài),如:重入時被調(diào)用。
            unexpected() 在函數(shù)拋出一個沒有在“異常規(guī)格申明”中申明的異常時被調(diào)用。

            運行庫提供了缺省terminate_handler()unexpected_handler()函數(shù)處理對應(yīng)的情況。你可以通過set_terminate()set_unexpected()函數(shù)替換庫的默認(rèn)版本。這兩個函數(shù),其可以獲取不帶輸入輸出參數(shù)的函數(shù),并且該函數(shù)會返回原terminate或者unexpected函數(shù)的地址指針。以便在使用中調(diào)用或者以后的恢復(fù)。另外,在terminate ()中。其必須不返回或者拋出異常。

            在介紹了EH的基本知識后讓我們來看看EH是如何工作的。

            一般來說當(dāng)發(fā)生函數(shù)調(diào)用的時候,都會進(jìn)行諸如,保存寄存器值,參數(shù)壓棧,創(chuàng)建被調(diào)函數(shù)堆棧等保護(hù)現(xiàn)場的工作,而在函數(shù)返回的時候則會進(jìn)行與此相反的恢復(fù)現(xiàn)場的工作。

            這樣,當(dāng)一個異常發(fā)生時,程序會在異常點處停止,然后開始搜索異常處理函數(shù),其過程同函數(shù)返回相同,延調(diào)用棧向上搜索,直到找到一個與異常對象類型像匹配的異常申明,并進(jìn)行相應(yīng)的異常處理函數(shù),在異常處理結(jié)束后,程序跳到異常處理函數(shù)所在try快最接近的下面一條語句開始執(zhí)行。如果沒有找到合適的異常申明,則最終會調(diào)用std :: unexpected(),并在其中調(diào)用std:terminate()直到abort(),程序被終止。

            這也就意味著C++對于異常處理的模式始終是終止的。

            例如:

            #include <iostream.h>
            static void func(int n)
             {
              if (n)
              throw 100;
             }

            extern int main()
             {
              try
               {
                func(1);
                cout<<"程序不會執(zhí)行到這里"<<endl;
               }
              catch(int)
               {
                cout<<"捕獲一個int型異常"<<endl;
               }
              catch(...)
               {
                cout<<"捕獲任意類型異常"<<endl;
               }

              cout<<"繼續(xù)執(zhí)行"<<endl;

            return 0;
            }

            該程序在運行時會打印如下信息:

            捕獲一個int型異常
            捕獲任意類型異常


            至于異常處理的另一種模式恢復(fù)模式。可以通過循環(huán)檢測直到結(jié)果滿意為止。但在實際中,往往產(chǎn)生異常的地方與異常處理函數(shù)距離可能會比較遠(yuǎn),在這種情況下恢復(fù)模式就不那么可行了。

            雖然,在異常處理延調(diào)用棧向上走的過程中回析構(gòu)所有棧上的對象,但其并不會對堆中的對象進(jìn)行處理,這樣將會引起嚴(yán)重的資源泄露問題。

            例如:

            void func()
            {
                testclass *p = new testclass();
                ...
                test(p); //這里會拋出異常
               
            ...
                delete p; //在拋出異常后,這里不會被執(zhí)行,因此會導(dǎo)致內(nèi)存泄露問題。
            }

            為了解決這個問題,C++提供了std::auto_ptr模板。其原理就是,將指針用一個棧上的模版實例保護(hù)起來,當(dāng)發(fā)生異常的時候,模版會被析構(gòu),在析構(gòu)函數(shù)中指針也就被delete了。

            例如:

            void func()
            {
                std::auto_ptr<testclass> p(new testclass());
                ...
                test(p.get());
                ...
            }

            另外,在構(gòu)造函數(shù)中拋出異常并不會引發(fā)析構(gòu)函數(shù)。這一點要十分注意。因為這也會產(chǎn)生資源泄露問題。

            例如:

            class test
             {
              public:
              test() { c = new char[10]; throw -1;}
              ~test() {delete c;}

              private:
              char *c;
             };

            void proc()
             {
              try{
                 test t;
                }
              catch(int)
                {
                .......
                 }
             }


            由于異常是在test的構(gòu)造函數(shù)中產(chǎn)生的,因此其不會引發(fā)其析構(gòu)函數(shù)的調(diào)用。于是就如程序所示,產(chǎn)生了內(nèi)存泄露問題。對于這種問題,最好的解決辦法還是使用auto_ptr

            對于析構(gòu)函數(shù),則不要在其中拋出異常。其原因在于析構(gòu)函數(shù)會在其他異常拋出時被調(diào)用,這樣就會引發(fā)異常的重入問題,進(jìn)而導(dǎo)致terminate()被調(diào)用。如果在析構(gòu)函數(shù)中真要拋出異常,如:析構(gòu)函數(shù)調(diào)用的函數(shù)會拋出異常等,則必須在該析構(gòu)函數(shù)內(nèi)將其捕獲。

            前面說到要“找到一個與異常對象類型像匹配的異常申明”。事實上,這種匹配并不要求的十分準(zhǔn)確。
            考慮如下例子:

            #include <iostream.h>

            class base
              {
               public:
               virtual void what()
                {
                 cout << "base" << endl;
                }
              };

            class derived: public base
              {
               public:
               void what()
                {
                 cout << "derived" << endl;
                }
              };

            void f()
              {
               throw derived();
              }

            main()
              {
               try
                {
                 f();
                }
               catch(base b)
                {
                 b.what();
                }

               try
                {
                 f();
                }
               catch(base& b)
                {
                 b.what();
                }
            }

            其顯示結(jié)果為:

            base
            derived


            為什么會這樣呢。因為如果異常拋出一個派生類對象,而恰好又其基類所捕獲到。那么該對象會被做"切片"處理。也就是說相對于基類,派生元素會被割下。在例子中derived的vptr會被設(shè)為base的virtual table。因此虛函數(shù)what就會呈現(xiàn)出這種行為。而當(dāng)通過引用捕獲時,得到的僅僅是其地址,對象不會被做切片處理。vptr因此也就不會發(fā)生變化,所以what仍然呈現(xiàn)出來derived的行為。

            因此,這也就提醒我們將基類處理放在最后,在實際中更有意義。因為這樣可以盡可能的在前面的處理中保存信息。

            posted on 2007-10-18 15:38 安帛偉 閱讀(495) 評論(0)  編輯 收藏 引用

            只有注冊用戶登錄后才能發(fā)表評論。
            網(wǎng)站導(dǎo)航: 博客園   IT新聞   BlogJava   博問   Chat2DB   管理


            亚洲成色www久久网站夜月| 国产精品久久久久久福利69堂| 一本大道久久a久久精品综合| 99久久国产热无码精品免费久久久久| 久久精品国产亚洲网站| 久久久久无码专区亚洲av| 日韩欧美亚洲国产精品字幕久久久| 国产精品久久久久久久久久影院 | 亚洲精品成人网久久久久久| 欧美日韩久久中文字幕| 国产精品美女久久久m| 日韩亚洲国产综合久久久| 国产高潮国产高潮久久久| 久久亚洲国产最新网站| 99国产精品久久| 中文字幕日本人妻久久久免费| 久久香蕉国产线看观看99| 久久久久久久久波多野高潮| 中文字幕亚洲综合久久2| 99精品久久久久久久婷婷| 精品无码久久久久久国产| 久久久久高潮毛片免费全部播放| 久久亚洲色一区二区三区| 国产精品久久久久久| 久久久久久毛片免费播放| 精品久久久久久久国产潘金莲 | 精品久久人人做人人爽综合| 99久久无色码中文字幕人妻| 久久久久婷婷| 国产精品伦理久久久久久| 久久99亚洲网美利坚合众国| 久久综合九色综合网站| 色悠久久久久久久综合网| 久久久久久青草大香综合精品| 久久99国产精一区二区三区| 97久久久久人妻精品专区| 久久久精品2019免费观看| 久久夜色精品国产欧美乱| 久久婷婷五月综合色奶水99啪| 久久99久国产麻精品66| 亚洲精品无码久久久久|