More Effective C++ 學習筆記(3)
阿新 • • 發佈:2020-12-28
技術標籤:More Effective C++exception程式人生經驗分享
異常
為什麼要使用exceptions?
exceptions無法被忽略,如果一個函式利用設定狀態變數或是利用返回錯誤碼的方式發出異常訊號,無法保證此函式呼叫者會檢查錯誤碼,於是程式
就會持續下去,從而遠離錯誤點,但是有了exceptions,如果沒有被捕獲到,則程式會立刻終止。
條款9:利用destructors避免記憶體洩漏
即避免使用普通指標而去轉向智慧指標,或者說將資源封裝到物件內,通常便可在異常出現時避免洩漏資源。
如下示例:GUI應用軟體中的某個函式,必須產生一個視窗顯示某些資訊:
void displayInfo(const Infor& info) { //WINDOW_HANDLE w(createWindow()); //建立 //... display info //展示資訊 //destroyWindow(w); //釋放資源 } //如果在建立與釋放中間發生異常,則w持有的視窗將會遺失,其它動態分配的資源也會遺失 //採用中間類,在其構造和解構函式中獲取和釋放資源 class WindowHandle { public: WindowHandle(WINDOW_HANDLE handle):w(handle){} ~WindowHandle(){destroyWindow(w);} operator WINDOW_HANDLE(){return w;} //隱式轉換符 WindowHandle -> WINDOW_HANDLE private: WINDOW_HANDLE w; WindowHandle(const WindowHandle&) = delete; WindowHandle& operator=(const WindowHandle&) = delete; }; //重寫display void dislayInfo2(const Infor& info) { WindowHandle(createWindow()); //建立 ... display info //展示資訊 }
條款10:在constructors內阻止資源洩漏
c++只會析構已經構造完成的物件,物件也只有在constructor執行完畢時才算構造完成,因此如果在constructor函式中發生異常的話此物件,因此
為解決在建構函式中處理異常,可以使用
- member initialization lists 結合私有成員函式
class Image; class BookEntry { public: //初始化列表呼叫私有成員函式來解決建構函式中丟擲的異常 BookEntry(const string& filename):imageFile(initImage(filename)){} ~BookEntry() {delete imageFile;} private: //私有化成員函式,捕獲異常 Image* initImage(const string& fileName) { try{ if(fileNmae != "") return new Image(fileName); } catch(...) { delete imageFile; throw; } } private: Image* imageFile; };
- 智慧指標初始化動態記憶體
class Image; class BookEntry { public: //使用智慧指標來解決建構函式中丟擲的異常,好處是不用擔心釋放 imageFile記憶體問題 BookEntry(const string& filename):imageFile(new Image(filename)){} ~BookEntry() {delete imageFile;} private: //私有化成員函式,捕獲異常 Image* initImage(const string& fileName) { try{ if(fileNmae != "") return new Image(fileName); } catch(...) { delete imageFile; throw; } } private: std::unique_ptr<Image> imageFile; };
條款11:禁止異常流出destructors之外
class Session
{
public:
void destoryFun();
~Session()
{
try
{
destroyFun();
}
catch(...){} //捕獲異常看起來什麼都沒做,但是阻止了異常外流
}
};
理由:
- 它可以避免terminate函式在異常傳播過程中棧展開機制被呼叫;
- 可以協助確保destructor完成其應該完成的所有事
條款12:瞭解 "丟擲異常"、"傳遞引數","呼叫函式" 的差異
class Widget;
void f1(Widget w);
void f2(Widget& w);
catch (Wdiget w)
catch (Wdiget& w)
catch (const Wdiget& w)
catch (Wdiget* w)
catch (const Wdiget* w)
- 函式引數和異常傳遞方式都有三種:按值、按引用、按指標
- 呼叫函式時,控制權最終會回到呼叫端;丟擲異常時控制權不會回到丟擲端
- 一個物件被丟擲異常時總是會發生複製,因為這樣會避免物件的瓦解風險,不論捕獲的異常是by value\by reference
- 被丟擲的物件被允許的轉換動作,比傳參到函式少,如 by reference方式捕獲 = by reference-to-const
- 異常的呼叫者或者丟擲者和被呼叫者或捕獲者之間存在的型別吻合規則,型別必須的絕對匹配,且第一個匹配成功者便執行
條款13:以 by reference方式捕獲異常
原因:
- 避免物件刪除問題
- 避開物件的切割問題
- 保留捕獲標準exceptions的能力 【虛擬函式】
- 約束了物件被複制的次數
class exception
{
public:
virtual const char* what() throw();
};
class runtime_error:public exception
{
virtual const char* what() throw()
{
...
}
};
//使用者自定義異常處理
class Validation_error
{
virtual const char* what() throw()
{
...
}
};
//throw Validation_error()
void doSomeThing()
{
try{
throw Validation_error();
}
catch(excepthon& e)
{
e.what(); //呼叫Validation_error類的實現函式
}
}