天天看點

C++ Primer 筆記——異常處理

1.棧展開過程沿着嵌套函數的調用鍊不斷查找,直到找到了與異常比對的catch句子為止,或者也可能一直沒找到比對的catch,則程式将調用terminate,退出主函數後查找過程終止。假設找到了一個catch,則執行其中的代碼,執行完之後,找到與try塊關聯的最後一個catch子句之後的點,并從這裡繼續執行。

2.如果在棧展開過程中推出了某個塊,編譯器将負責確定在這個塊中建立的對象能被正确銷毀,如果異常發生在構造函數中,即使某個對象隻構造了一部分,我們也要確定已構造的成員能被正确地銷毀。

3.在棧展開的過程中,運作類類型的局部對象的析構函數。因為這些析構函數是自動執行的,是以它們不應該抛出異常,如果要抛出異常,則應該在析構函數内部得到處理。一旦在棧展開的過程中析構函數抛出了異常,并且析構函數自身沒能捕獲到該異常,則程式将被終止。

4.異常對象是一種特殊對象,編譯器使用異常抛出表達式來對異常對象進行拷貝初始化。是以throw語句中的表達式必須擁有完全類型。而且如果該表達式是類類型的話,則相應的類必須含有一個可通路的析構函數和一個可通路的拷貝或移動構造函數。如果是數組類型或函數類型,則表達式将被轉換成與之對應的指針類型。

5.異常對象位于編譯器管理的空間中,編譯器確定無論最終調用的是哪個catch子句都能通路該空間。當異常處理完畢後,異常對象被銷毀。

6.當我們抛出一條表達式時,該表達式的靜态編譯時類型決定了異常對象的類型。如果throw解引用一個指向派生類的基類指針,則抛出的對象将被切掉派生類的部分。

7.聲明的類型決定了處理代碼所能捕獲的異常類型,這個類型必須是完全類型,它可以是左值引用,但不能是右值引用。

8.通常情況下,如果catch接受的異常與某個繼承體系有關,則最好将該catch的參數定義成引用類型。

9.在搜尋catch語句的過程中,挑選出來的應該是第一個與異常比對的catch語句,是以,越是專門的catch越應該置于整個catch清單前端。

10.異常聲明中絕大多數類型轉換都不被允許,除了以下幾點情況:

  • 允許從非常量向常量的類型轉換
  • 允許派生類向基類的類型轉換
  • 數組或函數被轉換成指針

11.有時候一個單獨的catch語句不能完整地處理某個異常,可以通過重新抛出的操作将異常傳遞給另外一個catch語句,這裡的重新抛出仍然是一條throw,隻不過不包含任何表達式,空throw語句隻能出現在catch語句或catch語句直接或間接調用的函數之内。如果在其他地方使用,編譯器将調用terminate。很多時候,catch語句會改變其參數的内容,隻有當異常聲明是引用類型時,重新抛出的參數才會保留改變的内容繼續傳播。

struct test
{
    int *id = nullptr;
    int *count = nullptr;
};

struct testex : public test
{
    
};

testex t;

void dotest()
{
    try
    {
        if (!t.id)
            throw t;  // 這裡實際上對t做了拷貝
    }
    catch (testex &e)
    {
        e.id = new int(1);
        if(!t.count)
            throw;
    }
}

int main()
{
    try
    {
        dotest();
    }
    catch (test e)
    {
        e.count = new int(1);    // 這裡的e的id已經指向1
    }

    // 注意,到這裡我們的全局變量t沒有任何變化,如果想要t被改變,我們應該throw t的指針,即throw &t;
    return 0;
}      

12.為了一次性捕獲所有的異常類型,我們使用省略号作為異常聲明,如果catch(...)與其他幾個catch語句一起出現,則catch(...)必須在最後的位置,出現在捕獲所有異常語句後面的catch語句将永遠不會被比對。

13.構造函數體内的catch語句無法處理構造函數初始值清單抛出的異常。我們可以将構造函數寫成函數try語句塊的形式,這樣既能處理構造函數體,也能處理構造函數的初始化過程。但是注意,在初始化構造函數的參數時也可能發生異常,這樣的異常不屬于函數try語句塊的一部分,是以無法處理。

14.在C++11新标準中,我們可以通過提供noexcept說明指定某個函數不會抛出異常。緊跟在函數的參數清單後面,要跟在const及引用限定符之後,在final,override或虛函數的=0之前。

15.編譯器并不會在編譯時檢查noexcept說明,如果一個函數在說明了noexcept的同時又含有throw語句或者調用了可能抛出異常的其他函數,編譯器将順利編譯過。一旦一個noexcept函數抛出了異常,程式就會調用terminate以確定遵守不在運作時抛出異常的承諾,上述過程對是否執行棧展開未作約定,是以noexcept可以用在兩種情況之下:

  • 我們确認函數不會抛出異常
  • 我們根本不知道該如何處理異常

16.noexcept說明符的實參常常與noexcept運算符混合使用,它是一個一進制運算符,傳回值是一個bool類型的右值常量表達式,用于表示給定的表達式是否會抛出異常。和sizeof類似,noexcept也不會求其運算對象的值。

void test() noexcept(true);    // 不會抛出異常

void test1()
{
    test();
}

void test2() noexcept(test1);    // test1調用的所有函數都做了不抛出說明并且test1本身不含有throw語句時,表達式為true      

17.如果我們為某個指針做了不抛出異常的聲明,則該指針将隻能指向不抛出異常的函數。如果一個虛函數承諾了它不會抛出異常,則後續派生出來的虛函數也必須做出同意的承諾。

void test() noexcept(true);    // 不會抛出異常
void test1() noexcept(false);    // 可能會抛出異常

void(*pf)(void) noexcept = test;    // 正确
void(*pf1)(void) noexcept = test1;    // 錯誤

class base
{
public:
    virtual void add(int) noexcept {};
};

class sub : public base
{
public:
    void add(int) {}    //錯誤
};      

18.标準庫異常類的繼承體系如下,我們可以直接使用也可以繼承它們定義自己的異常類型。

C++ Primer 筆記——異常處理

轉載于:https://www.cnblogs.com/zoneofmine/p/7442958.html