[導讀]我們在對 vector 做 push 操作的時候,或者對某個指針做 new 操作的時候,如果沒有做異常處理,一旦系統(tǒng)內存不夠用了,程序是會被 terminate 掉的。這就要求我們熟悉 C++ 異常,保證日常開發(fā)中能正確處理它。本文主要介紹C++ 異常機制的底層原理與實際應用,通俗易懂,快來讀一讀吧。
(給C語言與CPP編程加星標,提升C/C++技能)
作者:melonstreet ?整理:cpp開發(fā)者
出處:https://www.cnblogs.com/QG-whz/
【導讀】:
我們在對 vector 做 push 操作的時候,或者對某個指針做 new 操作的時候,如果沒有做異常處理,一旦系統(tǒng)內存不夠用了,程序是會被 terminate 掉的。這就要求我們熟悉 C++ 異常,保證日常開發(fā)中能正確處理它。本文主要介紹C++ 異常機制的底層原理與實際應用,通俗易懂,快來讀一讀吧。
以下是正文
C++異常機制概述
異常處理是C++的一項語言機制,用于在程序中處理異常事件。異常事件在 C++ 中表示為
異常對象
。
異常事件發(fā)生時,程序使用throw關鍵字拋出異常表達式,拋出點稱為異常出現(xiàn)點,由操作系統(tǒng)為程序設置當前異常對象,然后執(zhí)行程序的當前異常處理代碼塊,在包含了異常出現(xiàn)點的最內層的?
try?
塊,依次匹配catch語句中的異常對象(只進行類型匹配,catch參數(shù)有時在 catch 語句中并不會使用到)。若匹配成功,則執(zhí)行 catch 塊內的異常處理語句,然后接著執(zhí)行?
try...catch...?
塊之后的代碼。如果在當前的 try...catch... 塊內找不到
匹配
該異常對象的catch語句,則由更外層的 try...catch... 塊來處理該異常;如果當前函數(shù)內所有的 try...catch... 塊都不能匹配該異常,則遞歸回退到調用棧的上一層去處理該異常。如果一直退到主函數(shù) main() 都不能處理該異常,則調用系統(tǒng)函數(shù) terminate() 終止程序。
一個最簡單的 try...catch... 的例子如下所示。我們有個程序用來記班級學生考試成績,考試成績分數(shù)的范圍在 0-100 之間,不在此范圍內視為數(shù)據(jù)異常:
int main()
{
int score=0;
while (cin >> score)
{
try
{
if (score > 100 || score < 0)
{
throw score;
}
}
catch (int score)
{
cerr << "你輸入的分數(shù)數(shù)值有問題,請重新輸入!";
continue;
}
}
}
在上面這個示例中,
throw?
是個關鍵字,與拋出表達式構成了 throw 語句。
其語法為:
throw 語句必須包含在 try 塊中,也可以是被包含在調用棧的外層函數(shù)的 try 塊中,如:
void registerScore(int score)
{
if (score > 100 || score < 0)
throw score;
}
int main()
{
int score=0;
while (cin >> score)
{
try
{
registerScore(score);
}
catch (int score)
{
cerr << "你輸入的分數(shù)數(shù)值有問題,請重新輸入!";
continue;
}
}
}
執(zhí)行 throw 語句時,throw 表達式將作為對象被復制構造為一個新的對象,稱為異常對象。
異常對象放在內存的特殊位置,該位置既不是棧也不是堆,在 window 上是放在線程信息塊 TIB 中。
這個構造出來的新對象與本級的 try 所對應的 catch 語句進行
類型匹配
,類型匹配的原則在下面介紹。
在本例中,依據(jù) score 構造出來的對象類型為 int,與 catch(int score) 匹配上,程序控制權轉交到 catch 的語句塊,進行異常處理代碼的執(zhí)行。如果在本函數(shù)內與 catch 語句的類型匹配不成功,則在調用棧的外層函數(shù)繼續(xù)匹配,如此遞歸執(zhí)行直到匹配上 catch 語句,或者直到 main 函數(shù)都沒匹配上而調用系統(tǒng)函數(shù) terminate() 終止程序。
當執(zhí)行一個 throw 語句時,跟在 throw 語句之后的語句將不再被執(zhí)行,throw 語句的語法有點類似于 return,因此導致在調用棧上的函數(shù)可能提早退出。
異常對象
異常對象是一種特殊的對象,編譯器依據(jù)異常拋出表達式復制構造異常對象,這要求拋出異常表達式不能是一個不完全類型(一個類型在聲明之后定義之前為一個不完全類型。不完全類型意味著該類型沒有完整的數(shù)據(jù)與操作描述),而且可以進行復制構造,這就要求異常拋出表達式的復制構造函數(shù)(或移動構造函數(shù))、析構函數(shù)不能是私有的。
異常對象不同于函數(shù)的局部對象,局部對象在函數(shù)調用結束后就被自動銷毀,而異常對象將駐留在所有可能被激活的 catch 語句都能訪問到的內存空間中,也即上文所說的 TIB。當異常對象與 catch 語句成功匹配上后,在該 catch 語句的結束處被自動析構。
在函數(shù)中返回局部變量的引用或指針幾乎肯定會造成錯誤,同樣的道理,在 throw 語句中拋出局部變量的指針或引用也幾乎是錯誤的行為。如果指針所指向的變量在執(zhí)行 catch 語句時已經(jīng)被銷毀,對指針進行解引用將發(fā)生意想不到的后果。
throw 出一個表達式時,該表達式的靜態(tài)編譯類型將決定異常對象的類型。所以當 throw 出的是基類指針的解引用,而該指針所指向的實際對象是派生類對象,此時將發(fā)生派生類對象切割。
除了拋出用戶自定義的類型外,C++ 標準庫定義了一組類,用戶報告標準庫函數(shù)遇到的問題。這些標準庫異常類只定義了幾種運算,包括創(chuàng)建或拷貝異常類型對象,以及為異常類型的對象賦值。
標準異常類 |
描述 |
頭文件 |
exception |
最通用的異常類,只報告異常的發(fā)生而不提供任何額外的信息 |
exception |
runtime_error |
只有在運行時才能檢測出的錯誤 |
stdexcept |
rang_error |
運行時錯誤:產(chǎn)生了超出有意義值域范圍的結果 |
stdexcept |
overflow_error |
運行時錯誤:計算上溢 |
stdexcept |
underflow_error |
運行時錯誤:計算下溢 |
stdexcept |
logic_error |
程序邏輯錯誤 |
stdexcept |
domain_error |
邏輯錯誤:參數(shù)對應的結果值不存在 |
stdexcept |
invalid_argument |
邏輯錯誤:無效參數(shù) |
stdexcept |
length_error |
邏輯錯誤:試圖創(chuàng)建一個超出該類型最大長度的對象 |
stdexcept |
out_of_range |
邏輯錯誤:使用一個超出有效范圍的值 |
stdexcept |
bad_alloc |
內存動態(tài)分配錯誤 |
new |
bad_cast |
dynamic_cast類型轉換出錯 |
type_info |
catch 關鍵字
catch語句匹配被拋出的異常對象。如果 catch 語句的參數(shù)是引用類型,則該參數(shù)可直接作用于異常對象,即參數(shù)的改變也會改變異常對象,而且在 catch 中重新拋出異常時會繼續(xù)傳遞這種改變。如果 catch 參數(shù)是傳值的,則復制構函數(shù)將依據(jù)異常對象來構造catch 參數(shù)對象。在該 catch 語句結束的時候,先析構 catch 參數(shù)對象,然后再析構異常對象。
在進行異常對象的匹配時,編譯器不會做任何的隱式類型轉換或類型提升。除了以下幾種情況外,異常對象的類型必須與 catch 語句的聲明類型完全匹配:
允許從非常量到常量的類型轉換。
允許派生類到基類的類型轉換。
數(shù)組被轉換成指向數(shù)組(元素)類型的指針。
函數(shù)被轉換成指向函數(shù)類型的指針。
尋找 catch 語句的過程中,匹配上的未必是類型完全匹配那項,而在是最靠前的第一個匹配上的 catch 語句(我稱它為最先匹配原則)。所以,派生類的處理代碼 catch 語句應該放在基類的處理 catch 語句之前,否則先匹配上的總是參數(shù)類型為基類的 catch 語句,而能夠精確匹配的 catch 語句卻不能夠被匹配上。
在 catch 塊中,如果在當前函數(shù)內無法解決異常,可以繼續(xù)向外層拋出異常,讓外層catch 異常處理塊接著處理。此時可以使用不帶表達式的 throw 語句將捕獲的異常重新拋出:
被重新拋出的異常對象為保存在 TIB 中的那個異常對象,與 catch 的參數(shù)對象沒有關系,若 catch 參數(shù)對象是引用類型,可能在 catch 語句內已經(jīng)對異常對象進行了修改,那么重新拋出的是修改后的異常對象;
若catch參數(shù)對象是非引用類型,則重新拋出的異常對象并沒有受到修改。
使用 catch(...){} 可以捕獲所有類型的異常,根據(jù)最先匹配原則,catch(...){} 應該放在所有 catch 語句的最后面,否則無法讓其他可以精確匹配的 catch 語句得到匹配。通常在catch(...){} 語句中執(zhí)行當前可以做的處理,然后再重新拋出異常。注意,catch 中重新拋出的異常只能被外層的 catch 語句捕獲。
棧展開、RAII
其實棧展開已經(jīng)在前面說過,就是從異常拋出點一路向外層函數(shù)尋找匹配的 catch 語句的過程,尋找結束于某個匹配的 catch 語句或標準庫函數(shù) terminate。這里重點要說的是棧展開過程中對局部變量的銷毀問題。我們知道,在函數(shù)調用結束時,函數(shù)的局部變量會被系統(tǒng)自動銷毀,類似的,throw 可能會導致調用鏈上的語句塊提前退出,此時,語句塊中的局部變量將按照構成生成順序的逆序,依次調用析構函數(shù)進行對象的銷毀。例如下面這個例子:
class A
{
public:
A() :a(0){ cout << "A默認構造函數(shù)" << endl; }
A(const A& rsh){ cout << "A復制構造函數(shù)" << endl; }
~A(){ cout << "A析構函數(shù)" << endl; }
private:
int a;
};
int main()
{
try
{
A a ;
throw a;
}
catch (A a)
{
;
}
return 0;
}
程序將輸出:
定義變量 a 時調用了默認構造函數(shù),使用 a 初始化異常變量時調用了復制構造函數(shù),使用異常變量復制構造 catch 參數(shù)對象時同樣調用了復制構造函數(shù)。三個構造對應三個析構,也即 try 語句塊中局部變量 a 自動被析構了。然而,如果 a 是在自由存儲區(qū)上分配的內存時:
int main()
{
try
{
A * a= new A;
throw *a;
}
catch (A a)
{
;
}
getchar();
return 0;
}
程序運行結果:
同樣的三次構造,卻只調用了兩次的析構函數(shù)!說明 a 的內存在發(fā)生異常時并沒有被釋放掉,發(fā)生了內存泄漏。
RAII機制有助于解決這個問題,RAII(Resource acquisition is initialization,資源獲取即初始化)。它的思想是以對象管理資源。為了更為方便、魯棒地釋放已獲取的資源,避免資源死鎖,一個辦法是把資源數(shù)據(jù)用對象封裝起來。程序發(fā)生異常,執(zhí)行棧展開時,封裝了資源的對象會被自動調用其析構函數(shù)以釋放資源。C++ 中的智能指針便符合RAII。關于這個問題詳細可以看《Effective C++》條款13.
異常機制的一個合理的使用是在構造函數(shù)中。構造函數(shù)沒有返回值,所以應該使用異常機制來報告發(fā)生的問題。更重要的是,構造函數(shù)拋出異常表明構造函數(shù)還沒有執(zhí)行完,其對應的析構函數(shù)不會自動被調用,因此析構函數(shù)應該先析構所有所有已初始化的基對象,成員對象,再拋出異常。
C++ 類構造函數(shù)初始化列表的異常機制,稱為 function-try block。一般形式為:
myClass::myClass(type1 pa1)
try: _myClass_val (初始化值)
{
}
catch ( exception& err )
{
};
C++ 不禁止析構函數(shù)向外界拋出異常,但析構函數(shù)被期望不向外界函數(shù)拋出異常。析構函數(shù)中向函數(shù)外拋出異常,將直接調用 terminator() 系統(tǒng)函數(shù)終止程序。如果一個析構函數(shù)內部拋出了異常,就應該在析構函數(shù)的內部捕獲并處理該異常,不能讓異常被拋出析構函數(shù)之外??梢匀绱颂幚恚?/span>
若析構函數(shù)拋出異常,調用 std::abort() 來終止程序。
在析構函數(shù)中 catch 捕獲異常并作處理。
關于具體細節(jié),有興趣可以看《Effective C++》條款08:別讓異常逃離析構函數(shù)。
noexcept修飾符與noexcept操作符
noexcept 修飾符是 C++11 新提供的異常說明符,用于聲明一個函數(shù)不會拋出異常。編譯器能夠針對不拋出異常的函數(shù)進行優(yōu)化,另一個顯而易見的好處是你明確了某個函數(shù)不會拋出異常,別人調用你的函數(shù)時就知道不用針對這個函數(shù)進行異常捕獲。在 C++98中關于異常處理的程序中你可能會看到這樣的代碼:
void func() throw(int ,double ) {...}
void func() throw(){...}
這是 throw 作為函數(shù)異常說明,前者表示 func()這個函數(shù)可能會拋出 int 或 double 類型的異常,后者表示 func() 函數(shù)不會拋出異常。事實上前者很少被使用,在 C++11 這種做法已經(jīng)被摒棄,而后者則被 C++11 的 noexcept 異常聲明所代替:
void func() noexcept {...}
在 C++11 中,編譯器并不會在編譯期檢查函數(shù)的 noexcept 聲明,因此,被聲明為noexcept 的函數(shù)若攜帶異常拋出語句還是可以通過編譯的。
在函數(shù)運行時若拋出了異常,編譯器可以選擇直接調用 terminate() 函數(shù)來終結程序的運行,因此,noexcept 的一個作用是
阻止異常的傳播,提高安全性
.
上面一點提到了,我們不能讓異常逃出析構函數(shù),因為那將導致程序的不明確行為或直接終止程序。實際上出于安全的考慮,C++11 標準中讓類的析構函數(shù)默認也是 noexcept 的。同樣是為了安全性的考慮,經(jīng)常被析構函數(shù)用于釋放資源的 delete 函數(shù),C++11 也默認將其設置為 noexcept。
noexcept也可以接受一個常量表達式作為參數(shù),例如:
void func() noexcept(常量表達式);
常量表達式的結果會被轉換成 bool 類型,noexcept(bool) 表示函數(shù)不會拋出異常,noexcept(false) 則表示函數(shù)有可能會拋出異常。
故若你想更改析構函數(shù)默認的 noexcept聲明,可以顯式地加上 noexcept(false) 聲明,但這并不會帶給你什么好處。
異常處理機制的主要環(huán)節(jié)是運行期類型檢查。當拋出一個異常時,必須確定異常是不是從 try 塊中拋出。異常處理機制為了完善異常和它的處理器之間的匹配,需要存儲每個異常對象的類型信息以及 catch 語句的額外信息。由于異常對象可以是任何類型(如用戶自定義類型),并且也可以是多態(tài)的,獲取其動態(tài)類型必須要使用運行時類型檢查(RTTI),此外還需要運行期代碼信息和關于每個函數(shù)的結構。
當異常拋出點所在函數(shù)無法解決異常時,異常對象沿著調用鏈被傳遞出去,程序的控制權也發(fā)生了轉移。轉移的過程中為了將異常對象的信息攜帶到程序執(zhí)行處(如對異常對象的復制構造或者 catch 參數(shù)的析構),在時間和空間上都要付出一定的代價,本身也有不安全性,特別是異常對象是個復雜的類的時候。
異常處理技術在不同平臺以及編譯器下的實現(xiàn)方式都不同,但都會給程序增加額外的負擔,當異常處理被關閉時,額外的數(shù)據(jù)結構、查找表、一些附加的代碼都不會被生成,正是因為如此,對于明確不拋出異常的函數(shù),我們需要使用 noexcept 進行聲明。
關于C++異常機制,歡迎在評論中和我探討。覺得文章不錯,請點贊和在看支持我繼續(xù)分享好文。謝謝!
免責聲明:本文內容由21ic獲得授權后發(fā)布,版權歸原作者所有,本平臺僅提供信息存儲服務。文章僅代表作者個人觀點,不代表本平臺立場,如有問題,請聯(lián)系我們,謝謝!
本站聲明: 本文章由作者或相關機構授權發(fā)布,目的在于傳遞更多信息,并不代表本站贊同其觀點,本站亦不保證或承諾內容真實性等。需要轉載請聯(lián)系該專欄作者,如若文章內容侵犯您的權益,請及時聯(lián)系本站刪除。
9月2日消息,不造車的華為或將催生出更大的獨角獸公司,隨著阿維塔和賽力斯的入局,華為引望愈發(fā)顯得引人矚目。
關鍵字:
阿維塔
塞力斯
華為
加利福尼亞州圣克拉拉縣2024年8月30日 /美通社/ -- 數(shù)字化轉型技術解決方案公司Trianz今天宣布,該公司與Amazon Web Services (AWS)簽訂了...
關鍵字:
AWS
AN
BSP
數(shù)字化
倫敦2024年8月29日 /美通社/ -- 英國汽車技術公司SODA.Auto推出其旗艦產(chǎn)品SODA V,這是全球首款涵蓋汽車工程師從創(chuàng)意到認證的所有需求的工具,可用于創(chuàng)建軟件定義汽車。 SODA V工具的開發(fā)耗時1.5...
關鍵字:
汽車
人工智能
智能驅動
BSP
北京2024年8月28日 /美通社/ -- 越來越多用戶希望企業(yè)業(yè)務能7×24不間斷運行,同時企業(yè)卻面臨越來越多業(yè)務中斷的風險,如企業(yè)系統(tǒng)復雜性的增加,頻繁的功能更新和發(fā)布等。如何確保業(yè)務連續(xù)性,提升韌性,成...
關鍵字:
亞馬遜
解密
控制平面
BSP
8月30日消息,據(jù)媒體報道,騰訊和網(wǎng)易近期正在縮減他們對日本游戲市場的投資。
關鍵字:
騰訊
編碼器
CPU
8月28日消息,今天上午,2024中國國際大數(shù)據(jù)產(chǎn)業(yè)博覽會開幕式在貴陽舉行,華為董事、質量流程IT總裁陶景文發(fā)表了演講。
關鍵字:
華為
12nm
EDA
半導體
8月28日消息,在2024中國國際大數(shù)據(jù)產(chǎn)業(yè)博覽會上,華為常務董事、華為云CEO張平安發(fā)表演講稱,數(shù)字世界的話語權最終是由生態(tài)的繁榮決定的。
關鍵字:
華為
12nm
手機
衛(wèi)星通信
要點: 有效應對環(huán)境變化,經(jīng)營業(yè)績穩(wěn)中有升 落實提質增效舉措,毛利潤率延續(xù)升勢 戰(zhàn)略布局成效顯著,戰(zhàn)新業(yè)務引領增長 以科技創(chuàng)新為引領,提升企業(yè)核心競爭力 堅持高質量發(fā)展策略,塑強核心競爭優(yōu)勢...
關鍵字:
通信
BSP
電信運營商
數(shù)字經(jīng)濟
北京2024年8月27日 /美通社/ -- 8月21日,由中央廣播電視總臺與中國電影電視技術學會聯(lián)合牽頭組建的NVI技術創(chuàng)新聯(lián)盟在BIRTV2024超高清全產(chǎn)業(yè)鏈發(fā)展研討會上宣布正式成立。 活動現(xiàn)場 NVI技術創(chuàng)新聯(lián)...
關鍵字:
VI
傳輸協(xié)議
音頻
BSP
北京2024年8月27日 /美通社/ -- 在8月23日舉辦的2024年長三角生態(tài)綠色一體化發(fā)展示范區(qū)聯(lián)合招商會上,軟通動力信息技術(集團)股份有限公司(以下簡稱"軟通動力")與長三角投資(上海)有限...
關鍵字:
BSP
信息技術
山海路引?嵐悅新程 三亞2024年8月27日 /美通社/ --?近日,海南地區(qū)六家凱悅系酒店與中國高端新能源車企嵐圖汽車(VOYAH)正式達成戰(zhàn)略合作協(xié)議。這一合作標志著兩大品牌在高端出行體驗和環(huán)保理念上的深度融合,將...
關鍵字:
新能源
BSP
PLAYER
ASIA
上海2024年8月28日 /美通社/ -- 8月26日至8月28日,AHN LAN安嵐與股神巴菲特的孫女妮可?巴菲特共同開啟了一場自然和藝術的療愈之旅。 妮可·巴菲特在療愈之旅活動現(xiàn)場合影 ...
關鍵字:
MIDDOT
BSP
LAN
SPI
8月29日消息,近日,華為董事、質量流程IT總裁陶景文在中國國際大數(shù)據(jù)產(chǎn)業(yè)博覽會開幕式上表示,中國科技企業(yè)不應怕美國對其封鎖。
關鍵字:
華為
12nm
EDA
半導體
上海2024年8月26日 /美通社/ -- 近日,全球領先的消費者研究與零售監(jiān)測公司尼爾森IQ(NielsenIQ)迎來進入中國市場四十周年的重要里程碑,正式翻開在華發(fā)展新篇章。自改革開放以來,中國市場不斷展現(xiàn)出前所未有...
關鍵字:
BSP
NI
SE
TRACE
上海2024年8月26日 /美通社/ -- 第二十二屆跨盈年度B2B營銷高管峰會(CC2025)將于2025年1月15-17日在上海舉辦,本次峰會早鳥票注冊通道開啟,截止時間10月11日。 了解更多會議信息:cc.co...
關鍵字:
BSP
COM
AI
INDEX
上海2024年8月26日 /美通社/ -- 今日,高端全合成潤滑油品牌美孚1號攜手品牌體驗官周冠宇,開啟全新旅程,助力廣大車主通過駕駛去探索更廣闊的世界。在全新發(fā)布的品牌視頻中,周冠宇及不同背景的消費者表達了對駕駛的熱愛...
關鍵字:
BSP
汽車制造
此次發(fā)布標志著Cision首次為亞太市場量身定制全方位的媒體監(jiān)測服務。 芝加哥2024年8月27日 /美通社/ -- 消費者和媒體情報、互動及傳播解決方案的全球領導者Cis...
關鍵字:
CIS
IO
SI
BSP
上海2024年8月27日 /美通社/ -- 近來,具有強大學習、理解和多模態(tài)處理能力的大模型迅猛發(fā)展,正在給人類的生產(chǎn)、生活帶來革命性的變化。在這一變革浪潮中,物聯(lián)網(wǎng)成為了大模型技術發(fā)揮作用的重要陣地。 作為全球領先的...
關鍵字:
模型
移遠通信
BSP
高通
北京2024年8月27日 /美通社/ -- 高途教育科技公司(紐約證券交易所股票代碼:GOTU)("高途"或"公司"),一家技術驅動的在線直播大班培訓機構,今日發(fā)布截至2024年6月30日第二季度未經(jīng)審計財務報告。 2...
關鍵字:
BSP
電話會議
COM
TE
8月26日消息,華為公司最近正式啟動了“華為AI百校計劃”,向國內高校提供基于昇騰云服務的AI計算資源。
關鍵字:
華為
12nm
EDA
半導體