C++ Primer
- what()函式返回 const char*,該pointer指向C-style字元字串,提供一些描述
- 如果丟出的Exception型別是"接收String初值"的,what()則會把string初值以C-style字元array傳回
- "Exception允許我們將[問題檢測]與[問題解決]分開,檢測單元不必知道如何處理該問題。"
- 我的理解是,一般以錯誤碼回報,就必須在接收到錯誤碼的當下進行問題解決,或者繼續往上拋,接到的地方再決定要解決還是往上拋,而Exception不同的是不管在哪裡[問題檢測]發生了,妳可以直接決定在要進行[問題解決]的位置才加上catch,而不是檢測到問題就必須進行[問題解決](可能是解決錯誤或往上拋)。
- 發生Exception的區塊其Local物件都會被銷毀,但在其區塊產生的Exception物件呢?
- 該Exception物件會由編譯器管理,確保位於任何位置的catch都可以取得。
- "如果拋出的Exception是derived-type,但被一個接受base-type的catch處理,那麼這個catch就不能使用該derived-type的專屬成員。"
- 這邊提醒要注意例外類別的繼承關係,如果父類別接收了子類別的例外物件,就無法使用子類別的專屬成員,也就是這個物件會被切割,只拋出父類別的部分。
- 堆疊輾轉開解
- 白話來說就是巢狀的try-catch,如果throw找不到對應的try-catch區段,就會銷毀所有local變數,並往上一層繼續找,而一但找到匹配的cattch區塊並完成工作,就會從同一個try-catch群組中的最後一個catch子句的下一行開始執行,要注意的是,接續下去的工作流程位置已改變,可能已經偏離原本throw位置已經好幾層,要特別留意流程邏輯。
- 在throw觸發時,該local的變數會被銷毀,該區域的物件也會呼叫出解構式進行銷毀,但解構式又觸發了exception呢?
- 如果解構式拋出一個自己並不處理的exception,會導致標準庫的terminate()被呼叫,通常terminate()會呼叫abort(),強制結束整個程式,但其實解構式主要用來釋放資源,所以不太可能拋出exception,標準庫提供的各個classes都以確保他們的解構式不會引發異常。
- C++中,函式宣告的部分也可以加上throw(...)以及throw()來代表此函示會丟出什麼類型的exception或不丟出任何exception,可是和java不同的是,編譯時期他並不會幫你檢查會拋出的exception和宣告的是否相同,只有執行時期才會檢測出來。
- 如果try區塊拋出位列於宣告throw內的exception,則它會引發unexpected(),預設情況下會呼叫terminate()來中止程式。
- 這樣的用法等同保證程式在"拋出的異常違背你所宣告的規格時,unexpected()會被呼叫"。
- 如果catch區塊只需知道exception型別就可以處理,可以不必寫出引數名稱,反之如果需要更多資訊,就需要包含引數名稱,好讓catch透過這個名稱存取異常物件,簡單展示如下:
catch(exception)
2.透過引數名稱存取異常物件
catch(exception e)
e.what();
- Exception的三種形式:non-reference、reference、pointer個別的操作方式
- 這部分我一開始搞混了,後來找到參考的部落格,覺得清楚明瞭,所以直接把部分內容紀錄在這裡。
- 範例參考網址(https://blog.csdn.net/u014038273/article/details/77816762)
1.透過pointer傳遞:
該部落格文章說利用pointer傳遞效率應該會最高,但普遍為什麼都不這麼做呢?便舉下方例子。
class exception{...};void someFunction(){ static exception ex; ... throw &ex; ...}void doSomething(){ try { someFunction(); } catch(exception* ex) { ...
}}上面是一個使用pointer傳遞exception的例子,但很可能因為粗心而寫成下列方式:
void someFunction(){ exception ex; ... throw &ex; ...
}這會有什麼問題?如同前面提到的,throw觸發時區域變數是會被銷毀的,所以ex這個exception物件就變成指向"沒有物件"的位址,根據部落格是稱作"殭屍指針",我不清楚台灣的用語是什麼。而避免這種情形的方式可以寫成下列形式:
void someFunction(){ ...
throw new exception;
...
}這樣就可以避免傳出去的exception是區域變數了,這點也是Primer C++內不斷強調的。但這樣又會遇到另一個問題,詳細還是看該部落格的說明吧,總之目前來說pointer方式傳遞exception是不適合的。2.透過non-reference傳遞,也就是傳值,由於他是以複製值的方式傳遞,因此效率較低,且該部落格提到會產生Slicing problem,我猜測大概就是上面提到的丟出derived-type與接受base-type會產生的問題,物件會被切割成只剩下父類別的資訊,實際操作方式如下,但我想較適當的方式應該會第三種,reference方式傳遞。
class Validation_error:public runtime_error:{public: virtual const char* what() whrow();
...
}void someFunction(){ ...
if(a validation 測試失敗) { throw Validation_error();
}
...
}void doSomething(){ try { someFunction(); } catch(exception ex) { cerr<<ex.what();
...
}
}
3.最後是透過reference的方式傳遞,根據該部落格說明,可以避免slicing的問題,同時也不會有不小心刪除區域的exception變數導致指標找不到目的地,且效率也較non-reference高,又能夠修改exception後再繼續傳遞出去,應該是最適用於各種情境的傳遞方式了。注意使用方式和pointer的差別,只是在catch的部分加一個&符號而已。
void someFunction(){ ...
if(a validation 測試失敗)
{
throw Validation_error();
}
...
}void doSomething(){ try { someFunction(); }
catch(exception &ex) { cerr<<ex.what();
...
}
}
- C++ 預處理器除錯技巧
- 使用預處理器(Prepeocessor)協助除錯 (剛好和例外章節一起看因此一併紀錄)
- 利用預處理器變數 "NDEBUG" 寫出條件性的除錯程式碼,例如:
#ifndef NDEBUG cerr << "starting main" << endl;#endif- "NDEBUG"是C++內部已經定義好的,因此很多地方都此預處理器來判斷是否執行。
- 例如 assert() function,我們常用它來檢查new出來的物件是否成功,書中提到他能夠檢查結果是true或false,但這會消耗執行時期的成本,因此如果你已經要進行release,就可以定義"NDEBUG",assert() 就不會執行。
- 其他四個常用的預處理器
- __FILT__ : 檔案的名稱
- __LINE__ : 目前的行號
- __TIME__ : 檔案編譯時間
- __DATE__ : 檔案編譯日期
