首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> C++ >> 關於C++

如何編寫異常安全的 C++ 代碼:三級保證與 RAII 實戰

     編輯:關於C++

何時拋出異常:不變式視角

  • 前條件:調用前必須滿足,違反 = 邏輯錯誤(斷言/終止)。
  • 後條件:調用後應滿足,違反 = 異常狀況(拋出異常)。
  • 不變式:執行全程保持。
  • 定義:滿足前條件但無法滿足後條件 → 異常狀況 → 拋出異常。

異常安全三級保證

級別保證內容
基本保證異常後對象處於「未知但有效」狀態(不變式完整)。
強保證操作具備事務性:要么成功達到目標狀態,要么狀態不變(回滾)。
不拋異常操作保證不拋出異常。析構、釋放、swap 必須達此級別。

核心技術:RAII(資源獲取即初始化)

錯誤模式:

Type* p = new Type;
try { ... } catch(...) { delete p; throw; }

正確模式:

auto_ptr<Type> p(new Type); // 或 unique_ptr/shared_ptr
do_something(); // 異常自動釋放,無需 try-catch

原則:不關心/無法處理的異常,不要捕獲。大多數異常安全代碼無 try-catch。

構造函數的異常安全

成員為原始指針時:

Type() : m_a(new TypeA), m_b(new TypeB) {} // 危險:new B 失敗導致 A 洩漏

改進:智能指針成員(shared_ptr/scoped_ptr)自動管理:

shared_ptr<TypeA> m_a; shared_ptr<TypeB> m_b;
Type() : m_a(new TypeA), m_b(new TypeB) {} // 安全

成對操作的 RAII 封裝

// 鎖
struct scoped_lock {
explicit scoped_lock(Lock& l): m_l(l) { m_l.lock(); }
~scoped_lock() { m_l.unlock(); }
Lock& m_l;
};
scoped_lock guard(a_lock); // 自動加解鎖

通用模板 pair_guard 適配任意成對函數(配合 boost::bind/std::bind)。

實現基本保證:保持對象合法或進入非法後立即恢復

// 字符串賦值基本保證版
myalloc pool(m_data);
pool.deallocate(m_data); // 進入非法
m_data = nullptr; // 立即恢復合法(空串)
if (!rsh.empty()) {
m_data = pool.allocate(rsh.size()+1);
never_failed_copy(m_data, rsh.m_data, rsh.size()+1);
}

實現強保證:副本-交換惯用法

// 字符串賦值強保證版
myalloc pool(m_data);
char* tmp = nullptr;
if (!rsh.empty()) {
tmp = pool.allocate(rsh.size()+1);
never_failed_copy(tmp, rsh.m_data, rsh.size()+1); // 先構建目標狀態
}
swap(tmp, m_data); // 原子切換到目標狀態(swap 不拋異常)
pool.deallocate(tmp); // 釋放舊資源

關鍵:在可能失敗的階段不修改對象,構建臨時目標狀態;在不拋異常階段(swap)提交。

區間操作強保證的代價

tmp = range;
for (itr = tmp.begin(); itr != tmp.end(); ++itr) itr->do_something();
swap(tmp, range);

需完整複製範圍,性能開銷大,視情況權衡。

不拋異常的基石

  • 內建類型賦值、取址、算術(除除法)不拋異常。
  • 析構、釋放、swap 必須保證不拋異常(標準庫容器 swap 不拋異常)。

異常安全清單

  1. 只在真正異常處拋出異常。
  2. 不知如何處理 → 不捕獲(或捕獲後重拋)。
  3. 充分利用 RAII 旁路異常。
  4. 追求強保證,底線基本保證。
  5. 確保析構/釋放/swap 不拋異常。

語言細節

  • 勿 throw new Exception(洩漏),拋值 throw Exception()。
  • 捕獲用引用:catch(const std::exception& e)。
  • 避免異常規範(throw()),編譯器不強制執行,C++11 棄用。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved