函數參數宣告與 catch 子句參數宣告語法幾乎相同(Widget, Widget&, const Widget&, Widget*...),但底層機制截然不同。核心差異在於:函數呼叫控制權最終返回呼叫端,異常拋出則永不返回。
Widget localWidget;
cin >> localWidget; // 傳參考,無拷貝,操作直接作用於 localWidget
throw localWidget; // 拋出異常:無論 catch by value 或 reference,皆會拷貝一份暫時物件
原因: localWidget 離開作用域時會被解構。若直接傳遞原物件給 catch,接收到的將是已解構的「屍體」。C++ 標準強制異常物件必須被複製,存放於例外處理機制管理的儲存區(通常在堆上),確保 catch 接收到有效物件。
catch 參數)。catch 參數綁定該暫時物件。catch 無法修改原物件。
class Widget { ... };
class SpecialWidget : public Widget { ... };
SpecialWidget sw;
Widget& rw = sw;
throw rw; // 拋出類型為 Widget (rw 的靜態類型),而非 SpecialWidget!
拷貝建構函數依靜態類型 (static type) 選擇,而非動態類型。這與 C++ 其他拷貝語境一致,但極易導致物件切割,衍生類特有資料遺失。
catch (Widget& w) { throw; } // 重新拋出「當前異常物件」,類型保持原樣 (可能是 SpecialWidget),無額外拷貝
catch (Widget& w) { throw w; } // 拋出 w 的「新拷貝」,類型固定為 Widget (w 的靜態類型),有拷貝開銷
永遠使用 throw; 重新拋出,保留原始異常類型與效能。
函數參數:臨時物件不可綁定非 const 參考 (需 const Widget&)。
異常捕獲:允許 catch (Widget& w) 捕獲臨時異常物件。這是標準特例。
| 語境 | 允許的隱式轉換 |
|---|---|
| 函數呼叫 | 標準轉換、使用者定義轉換、int → double 等算術轉換 |
| 異常匹配 | 僅兩類: 1. 公開繼承關係 (派生類 → 基類) 2. 指標轉 void*/const void* |
throw 10; // int 異常
catch (double d) {} // 無法捕獲!不進行 int→double 轉換
catch (int i) {} // 必須精確匹配 int 或 int&
虛擬函數:依動態類型選最佳匹配 (最衍生的覆寫)。
catch 子句:依原始碼出現順序,第一個匹配即執行。
try { ... }
catch (logic_error& ex) { ... } // 基類,放前面會攔截所有派生類
catch (invalid_argument& ex) { ... } // 派生類,永不被執行!
正確順序: 派生類 catch 放前面,基類放後面。編譯器通常會警告順序錯誤。
| 特性 | 函數參數傳遞 | 異常傳遞 |
|---|---|---|
| 控制權流向 | 返回呼叫端 | 單向傳播,不返回 |
| 拷貝次數 | 傳值 1 次,傳參考 0 次 | 至少 1 次 (暫時物件),傳值 2 次 |
| 靜態/動態類型 | 參數類型固定 | 拷貝依靜態類型,易切割 |
| 隱式類型轉換 | 豐富 | 極少 (僅繼承與 void*) |
| 匹配策略 | 最佳匹配 (重載解析) | 最先匹配 (原始碼順序) |
| 臨時物件綁定 | 僅限 const 參考 | 可綁定非 const 參考 |
理解這些差異,才能正確設計異常介面、避免切割陷阱、撰寫高效的 catch 區塊。