C++ 異常處理是構建健壯軟體的基石。本文提供完整可編譯的示範程式,涵蓋自訂異常類別、多種拋出/捕獲策略、拷貝建構行為、記憶體管理、異常規格與最佳實踐。
#include "stdafx.h"
#include <stdlib.h>
#include <crtdbg.h>
#include <iostream>
// 記憶體洩漏檢測(Debug 版)
#define _CRTDBG_MAP_ALLOC
#ifdef _DEBUG
#define new new(_NORMAL_BLOCK, __FILE__, __LINE__)
#endif
// 自訂異常類別
class MyException {
public:
MyException(int errorId) : m_errorId(errorId) {
std::cout << "MyException ctor called" << std::endl;
}
MyException(const MyException& other) : m_errorId(other.m_errorId) {
std::cout << "Copy ctor called" << std::endl;
}
~MyException() {
std::cout << "~MyException dtor called" << std::endl;
}
int getErrorId() const { return m_errorId; }
private:
int m_errorId;
};
int main(int argc, char* argv[]) {
_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
int throwErrorCode = 110;
std::cout << "Input test code: " << std::endl;
std::cin >> throwErrorCode;
try {
if (throwErrorCode == 110) {
MyException myStru(110);
throw &myStru; // 拋出指標:不觸發拷貝建構
}
else if (throwErrorCode == 119) {
MyException myStru(119);
throw myStru; // 拋出物件:觸發拷貝建構(建立臨時物件)
}
else if (throwErrorCode == 120) {
MyException* pMyStru = new MyException(120);
throw pMyStru; // 拋出 new 物件指標:catch 中需手動 delete
}
else {
throw MyException(throwErrorCode); // 拋出臨時物件:兩次拷貝建構
}
}
catch (MyException* pEx) {
std::cout << "catch(MyException*) executed" << std::endl;
std::cout << "Error Code: " << pEx->getErrorId() << std::endl;
// 若為 new 物件需 delete,但異常物件通常在專用棧上,不需 delete
}
catch (MyException ex) {
std::cout << "catch(MyException) executed" << std::endl;
std::cout << "Error Code: " << ex.getErrorId() << std::endl;
}
catch (...) {
std::cout << "catch(...) executed" << std::endl;
throw; // 重新拋出給上層
}
int temp; std::cin >> temp; // 暫停
return 0;
}
| 拋出方式 | 代碼 | 拷貝建構次數 | 捕獲型別 | 備註 |
|---|---|---|---|---|
| 拋出區域物件指標 | throw &obj; | 0 | catch(T*) | 高效,但 obj 必須生命週期覆蓋 catch(區域變數通常滿足) |
| 拋出區域物件 | throw obj; | 1(建立臨時物件) | catch(T) | catch 再次拷貝建構接收,共 2 次拷貝(C++17 拷貝省略優化) |
| 拋出 new 物件指標 | throw new T; | 0 | catch(T*) | 不推薦:catch 必須 delete,否則洩漏;異常傳播中易忘記 |
| 拋出臨時物件 | throw T(arg); | 2(建立臨時→catch 拷貝) | catch(T) | 簡潔但開銷最大 |
throw 後,運行時沿呼叫棧向上尋找匹配的 catchmain 仍無匹配 catch,呼叫 std::terminate() 終止程式,**不保證析構函數執行**異常物件**不在函式棧上**,而是在**專用異常棧**上,因此能跨越多層函式傳播而不被棧清理銷毀。
catch (const MyException& ex) // 推薦
catch (MyException* pEx) // 次之,利用多型
catch (MyException ex) // 不推薦:額外拷貝、物件切割風險
catch 必須置於基類 catch **之前**void func() throw(MyException, std::bad_alloc); // 僅拋出列舉型別(C++11 棄用)
void func() throw(); // 不拋出任何異常(noexcept 對應)
void func(); // 可拋出任何異常
std::unexpected()(預設終止)noexcept + 文檔說明class ResourceHolder {
Handle* h1, *h2;
public:
ResourceHolder() {
h1 = acquire1();
if (!h1) throw std::runtime_error("h1 failed");
h2 = acquire2();
if (!h2) { release(h1); throw std::runtime_error("h2 failed"); } // 必須手動釋放 h1
}
~ResourceHolder() { release(h1); release(h2); } // 若建構完成會自動呼叫
};
鐵律:建構函數拋出異常前,**必須手動釋放已獲取資源**,因為析構函數**不會**被呼叫。
catch (...) {
log_error();
throw; // 保持原異常型別、原堆疊資訊重新拋出
}
std::exception**,覆寫 what()catch本文出自 “對影成三人” 博客,請務必保留此出處 https://ticktick.blog.51cto.com/823160/191881