解析C++編程中異常相關(guān)的堆棧展開和throw()異常規(guī)范
C++ 中的異常和堆棧展開
在 C++ 異常機(jī)制中,控制從 throw 語句移至可處理引發(fā)類型的第一個 catch 語句。在到達(dá) catch 語句時,throw 語句和 catch 語句之間的范圍內(nèi)的所有自動變量將在名為“堆棧展開”的過程中被銷毀。在堆棧展開中,執(zhí)行將繼續(xù),如下所示:
控制通過正常順序執(zhí)行到達(dá) try 語句。執(zhí)行 try 塊內(nèi)的受保護(hù)部分。
如果執(zhí)行受保護(hù)的部分的過程中未引發(fā)異常,將不會執(zhí)行 try 塊后面的 catch 子句。執(zhí)行將在關(guān)聯(lián)的 try 塊后的最后一個 catch 子句后面的語句上繼續(xù)。
如果執(zhí)行受保護(hù)部分的過程中或在受保護(hù)的部分調(diào)用的任何例程中引發(fā)異常(直接或間接),則從通過 throw 操作數(shù)創(chuàng)建的對象中創(chuàng)建異常對象。(這意味著,可能會涉及復(fù)制構(gòu)造函數(shù)。)此時,編譯器會在權(quán)限更高的執(zhí)行上下文中查找可處理引發(fā)的類型異常的 catch 子句,或查找可以處理任何類型的異常的 catch 處理程序。按照 catch 處理程序在 try 塊后面的顯示順序檢查這些處理程序。如果未找到適當(dāng)?shù)奶幚沓绦?,則檢查下一個動態(tài)封閉的 try 塊。此過程將繼續(xù),直到檢查最外面的封閉 try 塊。
如果仍未找到匹配的處理程序,或者在展開過程中但在處理程序獲得控制前發(fā)生異常,則調(diào)用預(yù)定義的運(yùn)行時函數(shù) terminate。如果在引發(fā)異常后但在展開開始前發(fā)生異常,則調(diào)用 terminate。
如果找到匹配的 catch 處理程序,并且它通過值進(jìn)行捕獲,則通過復(fù)制異常對象來初始化其形參。如果它通過引用進(jìn)行捕獲,則初始化參數(shù)以引用異常對象。在初始化形參后,堆棧的展開過程將開始。這包括對與 catch 處理程序關(guān)聯(lián)的 try 塊的開頭和異常的引發(fā)站點(diǎn)之間完全構(gòu)造(但尚未析構(gòu))的所有自動對象的析構(gòu)。析構(gòu)按照與構(gòu)造相反的順序發(fā)生。執(zhí)行 catch 處理程序且程序會在最后一個處理程序之后(即,在不是 catch 處理程序的第一個語句或構(gòu)造處)恢復(fù)執(zhí)行??刂浦荒芡ㄟ^引發(fā)的異常進(jìn)入 catch 處理程序,而絕不會通過 goto 語句或 switch 語句中的 case 標(biāo)簽進(jìn)入。
堆棧展開示例
以下示例演示引發(fā)異常時如何展開堆棧。線程執(zhí)行將從 C 中的 throw 語句跳轉(zhuǎn)到 main 中的 catch 語句,并在此過程中展開每個函數(shù)。請注意創(chuàng)建 Dummy 對象的順序,并且會在它們超出范圍時將其銷毀。還請注意,除了包含 catch 語句的 main 之外,其他函數(shù)均未完成。函數(shù) A 絕不會從其對 B() 的調(diào)用返回,并且 B 絕不會從其對 C() 的調(diào)用返回。如果取消注釋 Dummy 指針和相應(yīng)的 delete 語句的定義并運(yùn)行程序,請注意絕不會刪除該指針。這說明了當(dāng)函數(shù)不提供異常保證時會發(fā)生的情況。有關(guān)詳細(xì)信息,請參閱“如何:針對異常進(jìn)行設(shè)計(jì)”。如果注釋掉 catch 語句,則可以觀察當(dāng)程序因未經(jīng)處理的異常而終止時將發(fā)生的情況。
#include <string> #include <iostream> using namespace std; class MyException{}; class Dummy { public: Dummy(string s) : MyName(s) { PrintMsg("Created Dummy:"); } Dummy(const Dummy& other) : MyName(other.MyName){ PrintMsg("Copy created Dummy:"); } ~Dummy(){ PrintMsg("Destroyed Dummy:"); } void PrintMsg(string s) { cout << s << MyName << endl; } string MyName; int level; }; void C(Dummy d, int i) { cout << "Entering FunctionC" << endl; d.MyName = " C"; throw MyException(); cout << "Exiting FunctionC" << endl; } void B(Dummy d, int i) { cout << "Entering FunctionB" << endl; d.MyName = "B"; C(d, i + 1); cout << "Exiting FunctionB" << endl; } void A(Dummy d, int i) { cout << "Entering FunctionA" << endl; d.MyName = " A" ; // Dummy* pd = new Dummy("new Dummy"); //Not exception safe!!! B(d, i + 1); // delete pd; cout << "Exiting FunctionA" << endl; } int main() { cout << "Entering main" << endl; try { Dummy d(" M"); A(d,1); } catch (MyException& e) { cout << "Caught an exception of type: " << typeid(e).name() << endl; } cout << "Exiting main." << endl; char c; cin >> c; }輸出:
Entering main Created Dummy: M Copy created Dummy: M Entering FunctionA Copy created Dummy: A Entering FunctionB Copy created Dummy: B Entering FunctionC Destroyed Dummy: C Destroyed Dummy: B Destroyed Dummy: A Destroyed Dummy: M Caught an exception of type: class MyException Exiting main.
異常規(guī)范 (throw)
異常規(guī)范是在 C++11 中棄用的 C++ 語言功能。這些規(guī)范原本用來提供有關(guān)可從函數(shù)引發(fā)哪些異常的摘要信息,但在實(shí)際應(yīng)用中發(fā)現(xiàn)這些規(guī)范存在問題。證明確實(shí)有一定用處的一個異常規(guī)范是 throw() 規(guī)范。例如:
void MyFunction(int i) throw();
告訴編譯器函數(shù)不引發(fā)任何異常。它相當(dāng)于使用 __declspec(nothrow)。這種用法是可選的。
(C++11) 在 ISO C++11 標(biāo)準(zhǔn)中,引入了 noexcept 運(yùn)算符,該運(yùn)算符在 Visual Studio 2015 及更高版本中受支持。盡可能使用 noexcept 指定函數(shù)是否可能會引發(fā)異常。
Visual C++ 中實(shí)現(xiàn)的異常規(guī)范與 ISO C++ 標(biāo)準(zhǔn)有所不同。下表總結(jié)了 Visual C++ 的異常規(guī)范實(shí)現(xiàn):
異常規(guī)范 | 含義 |
---|---|
throw() | 函數(shù)不會引發(fā)異常。但是,如果從標(biāo)記為 throw() 函數(shù)引發(fā)異常,Visual C++ 編譯器將不會調(diào)用意外處理函數(shù)。如果使用 throw() 標(biāo)記一個函數(shù),則 Visual C++ 編譯器假定該函數(shù)不會引發(fā) C++ 異常,并相應(yīng)地生成代碼。由于 C++ 編譯器可能會執(zhí)行代碼優(yōu)化(基于函數(shù)不會引發(fā)任何 C++ 異常的假設(shè)),因此,如果函數(shù)引發(fā)異常,則程序可能無法正確執(zhí)行。 |
throw(...) | 函數(shù)可以引發(fā)異常。 |
throw(type) | 函數(shù)可以引發(fā) type 類型的異常。但是,在 Visual C++ .NET 中,這被解釋為 throw(...)。 |
如果在應(yīng)用程序中使用異常處理,則一定有一個或多個函數(shù)處理引發(fā)的異常。在引發(fā)異常的函數(shù)和處理異常的函數(shù)間調(diào)用的所有函數(shù)必須能夠引發(fā)異常。
函數(shù)的引發(fā)行為基于以下因素:
- 您是否在 C 或 C++ 下編譯函數(shù)。
- 您所使用的 /EH 編譯器選項(xiàng)。
- 是否顯式指定異常規(guī)范。
不允許對 C 函數(shù)使用顯式異常規(guī)范。
下表總結(jié)了函數(shù)的引發(fā)行為:
// exception_specification.cpp // compile with: /EHs #include <stdio.h> void handler() { printf_s("in handler\n"); } void f1(void) throw(int) { printf_s("About to throw 1\n"); if (1) throw 1; } void f5(void) throw() { try { f1(); } catch(...) { handler(); } } // invalid, doesn't handle the int exception thrown from f1() // void f3(void) throw() { // f1(); // } void __declspec(nothrow) f2(void) { try { f1(); } catch(int) { handler(); } } // only valid if compiled without /EHc // /EHc means assume extern "C" functions don't throw exceptions extern "C" void f4(void); void f4(void) { f1(); } int main() { f2(); try { f4(); } catch(...) { printf_s("Caught exception from f4\n"); } f5(); }
輸出:
About to throw 1 in handler About to throw 1 Caught exception from f4 About to throw 1 in handler
相關(guān)文章
C語言實(shí)現(xiàn)YUV文件轉(zhuǎn)JPEG格式
這篇文章主要為大家詳細(xì)介紹了如何利用C語言實(shí)現(xiàn)將YUV文件轉(zhuǎn)為JPEG格式,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2023-12-12C++實(shí)現(xiàn)小型復(fù)數(shù)計(jì)算器
這篇文章主要為大家詳細(xì)介紹了C++實(shí)現(xiàn)小型復(fù)數(shù)計(jì)算器,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2021-06-06基于Qt播放器的實(shí)現(xiàn)詳解(支持Rgb,YUV格式)
這篇文章主要為大家詳細(xì)介紹了如何利用Qt實(shí)現(xiàn)簡易的播放器,可以支持支持Rgb,YUV格式。文中的示例代碼講解詳細(xì),感興趣的小伙伴可以嘗試一下2022-12-12C語言數(shù)據(jù)結(jié)構(gòu)經(jīng)典10大排序算法刨析
這篇文章主要介紹了C語言中常用的10種排序算法及代碼實(shí)現(xiàn),開發(fā)中排序的應(yīng)用需要熟練的掌握,因?yàn)槭腔A(chǔ)內(nèi)容,那C語言有哪些排序算法呢?本文小編就來詳細(xì)說說,需要的朋友可以參考一下2022-02-02基于Sizeof與Strlen的區(qū)別以及聯(lián)系的使用詳解
本篇文章是對Sizeof與Strlen的區(qū)別以及聯(lián)系的使用進(jìn)行了詳細(xì)的介紹。需要的朋友參考下2013-05-05