Effective C++ 條款08_不止於此
別讓異常逃離解構函式
C++ 並不禁止解構函式吐出異常,但它也不鼓勵這樣做。
考慮下面程式碼:
class Widget {
public:
...
~Widget( ) {...} // 假設這裡吐出一個異常
};
void soSomething()
{
std::vector<Widget> vec;
...
} // vec 在這裡被自動銷燬
當 vector vec 被銷燬,它有責任銷燬其內含的所有 Widgets。假設 vec 內含10個Widgets,而在析構第一個元素期間,有一個異常被丟擲。其他九個 Widgets 還是應該被銷燬(否則它們儲存的任何資源都會發生洩漏),因此 vec 應該呼叫它們各個解構函式。但假設在那些呼叫期間,第二個 Widget 解構函式又丟擲異常。現在有兩個同時作用的異常,這對 C++ 而言太多了。在兩個異常同時存在的情況下,程式若不是結束就是導致不明確行為。本例會導致不明確行為。
這很容易理解,但如果你的解構函式必須執行一個動作,而該動作可能會在失敗是丟擲異常,該咋辦呢?舉個例子,假設你使用一個 class 來負責資料庫連線:
class DBConnection {
public:
...
static DBConnection create(); //返回 DBConnection 物件
void close(); // 關閉聯機;失敗則丟擲異常
};
- 為了確保使用者不忘記在 DBConnection 物件身上呼叫 close() ,我們可以這樣做,建立一個用來管理 DBConnection 資源的 class,並在其解構函式中呼叫 close。
程式碼如下:
class DBConn {
DBConnection db;
public:
...
~DBConn(){
db.close();
}
};
這樣便允許使用者寫出這樣的程式碼:
{ // 開啟一個區塊(block)。
DBConn dbc(DBConnection::create()); // 建立 DBConnection 物件並交給 DBConn 物件以便管理。
// 通過 DBConn 的介面使用 DBConnection 物件。
// 在區塊結束點,DBConn 物件被銷燬,因而自動為 DBConnection 物件呼叫 close
...
}
只要呼叫 close 成功,一切都美好,但如果該呼叫導致異常,DBConn 解構函式會傳播該異常,也就是允許它離開這個解構函式。那樣就會造成問題,因為那是丟擲了難以駕馭的麻煩。
下面給出兩種辦法解決這個問題
- 如果 close 丟擲異常就結束程式。通常通過呼叫 abort 完成:
DBConn::~DBConn( ){
try { db.close(); }
catch ( ... ){
// 製作運轉記錄,記下對 close 的呼叫失敗
std::abort(); // 使用 abort 可以搶先制 “ 不明確 ” 行為於死地,也就是強迫結束程式
}
}
- 吞下因呼叫 close 而發生的異常:
DBConn::~DBConn( ){
try { db.close(); }
catch ( ... ){
// 製作運轉記錄,記下對 close 的呼叫失敗
}
}
一般而言,這樣做是一個壞主意,因為它壓制了 “ 某些動作失敗 ” 的重要資訊!然而有時候吞下異常也比負擔 “ 草率結束程式 ” 或 “ 不明確行為帶來的風險 ” 好。
顯然這些方法都不具有吸引力。問題在於兩者都無法對 “ 導致 close丟擲異常 ” 的情況做出反應。
一種較佳策略是重新設計 DBConn 介面,使使用者有機會對可能出現的問題做出反應:
class DBConn {
public:
void close (){ // 供使用者使用的新函式
db.close();
closed = true;
}
~DBConn(){
if(!closed) {
try { // 關閉連線(如果使用者不那麼做(呼叫close())的話)
db.close();
}
catch(...){
// 製作運轉記錄,記下對 close 的呼叫失敗
...
}
}
}
private:
DBConnection db;
bool closed;
};
把呼叫 close 的責任從 DBConn 解構函式手上移到 DBConn 使用者手上,讓使用者有機會第一手處理問題。
最後請記住:
- 解構函式絕對不要吐出異常。如果一個被解構函式呼叫的函式可能丟擲異常,解構函式應該捕捉任何異常,然後吞下它們(不傳播)或結束程式。
- 如果使用者需要對某個操作函式執行期間丟擲的異常作出反應,那麼 class 應該提供一個普通函式(而非在解構函式中)執行該操作。
相關文章
- Effective Modern C++ 系列之 條款2: autoC++
- Effective c++條款11:在operator=中處理“自我賦值”C++賦值
- [讀書筆記][effective C++]條款30-inline的原理筆記C++inline
- SAP軟體的強大“遠不止於此”
- effective C++ : CHAPTER 8C++APT
- 【Effective Modern C++】索引C++索引
- Effective C++筆記C++筆記
- effective C++筆記1C++筆記
- 2022和平精英全國大賽完美落幕,電競圈不止於此!
- 《Effective C++》讀書筆記C++筆記
- 新一代Mac晶片即將來臨,蘋果野心不止於此Mac晶片蘋果
- Effective C++ 筆記(3)資源管理C++筆記
- Effective C++ 4.設計與宣告C++
- 不止於程式碼
- 學懂現代C++——《Effective Modern C++》之轉向現代C++C++
- 條款01: 視C++為一個語言聯邦C++
- 《Effective C++》閱讀總結(三):資源管理C++
- 不止Docker:8款容器管理開源方案Docker
- 《Effective C++》第三版-1. 讓自己習慣C++(Accustoming Yourself to C++)C++
- 學懂現代C++——《Effective Modern C++》之型別推導和autoC++型別
- 08_自相關
- 條款05: 瞭解c++默默編寫並呼叫哪些函式C++函式
- 《Effective C++》第三版-5. 實現(Implementations)C++
- 讀完Java名著《Effective Java》: 我整理了這50條技巧Java
- 08_橋接模式橋接模式
- 盒馬鮮生不止於生鮮
- 《Effective C++》閱讀總結(四): 設計、宣告與實現C++
- 《Effective C++》第三版-3. 資源管理(Resource Management)C++
- js除錯命令,不止於console.log()JS除錯
- 不止於物件導向的SOLID原則物件Solid
- c++切面條題目C++
- 條款24:若所有引數皆需型別轉換,請為此採用non-member函式型別函式
- 《Effective C++》第三版-4. 設計與宣告(Design and Declarations)C++
- 《Effective Python 第二版》第二條 遵循PEP8風格指南Python
- 08_楊輝三角
- 《Effective C++》閱讀總結(二):類的構造、析構和賦值C++賦值
- 《看雪服務條款》
- C/C++關於結構的緊湊填充的幾條最佳實踐C++