2020年9月24日 星期四

[C++] C++ Primer 例外處理閱讀筆記

 


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透過這個名稱存取異常物件,簡單展示如下:
1.不須引數名稱 
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__ : 檔案編譯日期

[C#] Essential C# 7.0 例外處理閱讀筆記


Essential C# 7.0
  • 重新拋出異常
    • 如書中所述:"如選擇拋出具體異常或例外,會更新所有堆疊資訊來匹配新的拋出位置。這會導致只是意腸胃出發生位置的所有堆疊資訊丟失,使異常變得更難診斷。",白話來說,如果Catch當下無法處理掉錯誤,那麼就應該原封不動的將Exception丟出去(只使用throw;),不要另外新建立其他的例外(throw exception;),這樣才不會把原本錯誤的資訊給覆蓋掉了。
  • 如果攔截到不能處理的異常,那就代表這個位置不應該攔截。
  • 避免在呼叫堆疊較低的位置報告或記錄異常
    • 和上一條相呼應,由於太底層,可能資訊也不會很足夠表現該異常,因此在可以解決exception的地方再設置try-catch會比較合適。

[C++ / C#] Clean Code 例外處理閱讀筆記

近期正在看Clean Code,
前幾章提到的大多是局部性的程式撰寫建議,
我也把這本書放在工作桌旁,隨時修正現在的撰寫習慣,
前幾章的例子很快地就能說服我這樣的作法是較好的,
其實在這本書剛出版時就已經購入了,
但當時還是學生,沒寫過較具規模的程式,
因此看這本書時一點體會都沒有,很快地就看不下去。

社會新鮮人如何投資?

我的觀點是,在 沒有很多 本錢 的情況下, 別寄望每個月幾千元放到股票或者最近很夯的高股息ETF就能讓你致富, 先投資自己,讓自己的本業收入提高吧。