C++ noexcept異常說明及其使用


noexcept異常說明

 

 

noexcept異常使用

  相比於斷言適用於排除邏輯上不可能存在的狀態,異常通常是用於邏輯上可能發生的錯誤。在C++98中,我們看到了一套完整的不同於C的異常處理系統。通過這套異常處理系統,C++擁有了遠比C強大的異常處理功能。

在異常處理的代碼中,程序員有可能看到過如下的異常聲明表達形式:

void excpt_func() throw(int, double) { ... }

  在excpt_func函數聲明之后,我們定義了一個 動態異常聲明throw(int, double), 該聲明指出了excpt_func可能拋出的異常的類型。事實上,該特性很少被使用,因此在C++11中被棄用了(參見附錄B),而表示函數不會拋出異常的動態異常聲明throw()也被新的noexcept異常聲明所取代。

  noexcept形如其名地,表示其修飾的函數不會拋出異常。 不過與throw()動態異常聲明不同的是,在C++11中如果noexcept修飾的函數拋出了異常,編譯器可以選擇直接調用std::terminate()函數來終止程序的運行,這比基於異常機制的throw()在效率上會高一些。 這是因為異常機制會帶來一些額外開銷,比如函數拋出異常,會導致函數棧被依次地展開(unwind),並依幀調用在本幀中已構造的自動變量的析構函數等。

  從語法上講,noexcept修飾符有兩種形式,一種就是簡單地在函數聲明后加上noexcept關鍵字。比如:

void excpt_func() noexcept;

  另外一種則可以接受一個常量表達式作為參數,如下所示:

void excpt_func() noexcept (常量表達式);

  常量表達式的結果會被轉換成一個bool類型的值。該值為true,表示函數不會拋出異常,反之,則有可能拋出異常。這里, 不帶常量表達式的noexcept相當於聲明了noexcept(true),即不會拋出異常。

  在通常情況下,在C++11中使用noexcept可以有效地阻止異常的傳播與擴散。我們可以看看下面這個例子,如代碼清單2-12所示。

 1 #include <iostream>
 2 using namespace std;  3 void Throw() { throw 1; }  4 void NoBlockThrow() { Throw(); }  5 void BlockThrow() noexcept { Throw(); }  6  
 7 int main() {  8     try {  9  Throw(); 10  } 11     catch(...) { 12         cout << "Found throw." << endl;     // Found throw.
13  } 14  
15     try { 16  NoBlockThrow(); 17  } 18     catch(...) { 19         cout << "Throw is not blocked." << endl;    // Throw is not blocked.
20  } 21  
22     try { 23         BlockThrow();   // terminate called after throwing an instance of 'int'
24  } 25     catch(...) { 26         cout << "Found throw 1." << endl; 27  } 28 }

  在上述程序中,我們定義了Throw函數,該函數的唯一作用是拋出一個異常。而NoBlockThrow是一個調用Throw的普通函數,BlockThrow則是一個noexcept修飾的函數。從main的運行中我們可以看到,NoBlockThrow會讓Throw函數拋出的異常繼續拋出,直到main中的catch語句將其捕捉。而BlockThrow則會直接調用std::terminate中斷程序的執行,從而阻止了異常的繼續傳播。從使用效果上看,這與C++98中的throw()是一樣的。

  而noexcept作為一個操作符時,通常可以用於模板。比如:

1 template <class T>
2 void fun() noexcept(noexcept(T())) {}

  這里, fun函數是否是一個noexcept的函數,將由T()表達式是否會拋出異常所決定。這里的第二個noexcept就是一個noexcept操作符。當其參數是一個有可能拋出異常的表達式的時候,其返回值為false,反之為true(實際noexcept參數返回false還包括一些情況,這里就不展開講了)。這樣一來,我們就可以使模板函數根據條件實現noexcept修飾的版本或無noexcept修飾的版本。從泛型編程的角度看來,這樣的設計保證了關於“函數是否拋出異常”這樣的問題可以通過表達式進行推導。因此這也可以視作C++11為了更好地支持泛型編程而引入的特性。

  雖然noexcept修飾的函數通過std::terminate的調用來結束程序的執行的方式可能會帶來很多問題,比如無法保證對象的析構函數的正常調用,無法保證棧的自動釋放等,但很多時候,“暴力”地終止整個程序確實是很簡單有效的做法。 事實上,noexcept被廣泛地、系統地應用在C++11的標准庫中,用於提高標准庫的性能,以及滿足一些阻止異常擴散的需求。

  比如在C++98中,存在着使用throw()來聲明不拋出異常的函數

1 template<class T> class A { 2 public: 3 static constexpr T min() throw() { return T(); } 4 static constexpr T max() throw() { return T(); } 5 static constexpr T lowest() throw() { return T(); } 6 ...

  而在C++11中,則使用noexcept來替換throw()。

1 template<class T> class A { 2 public: 3 static constexpr T min() noexcept { return T(); } 4 static constexpr T max() noexcept { return T(); } 5 static constexpr T lowest() noexcept { return T(); } 6 ...
又比如,在C++98中,new可能會包含一些拋出的std::bad_alloc異常。
1 void* operator new(std::size_t) throw(std::bad_alloc); 2 void* operator new[](std::size_t) throw(std::bad_alloc);

而在C++11中,則使用noexcept(false)來進行替代。

1 void* operator new(std::size_t) noexcept(false); 2 void* operator new[](std::size_t) noexcept(false);

當然,noexcept更大的作用是保證應用程序的安全。比如 一個類析構函數不應該拋出異常,那么對於常被析構函數調用的delete函數來說,C++11默認將delete函數設置成noexcept,就可以提高應用程序的安全性。

1 void operator delete(void*) noexcept; 2 void operator delete[](void*) noexcept;

  而同樣出於安全考慮,C++11標准中讓類的析構函數默認也是noexcept(true)的。當然,如果程序員顯式地為析構函數指定了noexcept,或者類的基類或成員有noexcept(false)的析構函數,析構函數就不會再保持默認值。我們可以看看下面的例子:

 1 #include <iostream>
 2 using namespace std;  3  
 4 struct A {  5     ~A() { throw 1; }  6 };  7  
 8 struct B {  9     ~B() noexcept(false) { throw 2; } 10 }; 11  
12 struct C { 13  B b; 14 }; 15  
16 int funA() { A a; } 17 int funB() { B b; } 18 int funC() { C c; } 19  
20 int main() { 21     try { 22  funB(); 23  } 24     catch(...){ 25         cout << "caught funB." << endl; // caught funB.
26  } 27  
28     try { 29  funC(); 30  } 31     catch(...){ 32         cout << "caught funC." << endl; // caught funC.
33  } 34  
35     try { 36         funA(); // terminate called after throwing an instance of 'int'
37  } 38     catch(...){ 39         cout << "caught funA." << endl; 40  } 41 }

  在代碼中,無論是析構函數聲明為noexcept(false)的類B,還是包含了B類型成員的類C,其析構函數都是可以拋出異常的。只有什么都沒有聲明的類A,其析構函數被默認為noexcept(true),從而阻止了異常的擴散。這在實際的使用中,應該引起程序員的注意。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2026 CODEPRJ.COM