天天看點

《深入了解C++11:C++ 11新特性解析與應用》——2.6 noexcept修飾符與noexcept操作符

類别:庫作者

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

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

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

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

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

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

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

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

《深入了解C++11:C++ 11新特性解析與應用》——2.6 noexcept修飾符與noexcept操作符

在代碼清單2-12中,我們定義了throw函數,該函數的唯一作用是抛出一個異常。而noblockthrow是一個調用throw的普通函數,blockthrow則是一個noexcept修飾的函數。從main的運作中我們可以看到,noblockthrow會讓throw函數抛出的異常繼續抛出,直到main中的catch語句将其捕捉。而blockthrow則會直接調用std::terminate中斷程式的執行,進而阻止了異常的繼續傳播。從使用效果上看,這與c++98中的throw()是一樣的。

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

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

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

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

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

又比如,在c++98中,new可能會包含一些抛出的std::bad_alloc異常。

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

當然,noexcept更大的作用是保證應用程式的安全。比如一個類析構函數不應該抛出異常,那麼對于常被析構函數調用的delete函數來說,c++11預設将delete函數設定成noexcept,就可以提高應用程式的安全性。

而同樣出于安全考慮,c++11标準中讓類的析構函數預設也是noexcept(true)的。當然,如果程式員顯式地為析構函數指定了noexcept,或者類的基類或成員有noexcept(false)的析構函數,析構函數就不會再保持預設值。我們可以看看下面的例子,如代碼清單2-13所示。

《深入了解C++11:C++ 11新特性解析與應用》——2.6 noexcept修飾符與noexcept操作符

在代碼清單2-13中,無論是析構函數聲明為noexcept(false)的類b,還是包含了b類型成員的類c,其析構函數都是可以抛出異常的。隻有什麼都沒有聲明的類a,其析構函數被預設為noexcept(true),進而阻止了異常的擴散。這在實際的使用中,應該引起程式員的注意。

繼續閱讀