C語言本身沒有處理異常的機制,通常需要通過錯誤碼(error)、assert、全局變量、函數返回值等方法處理錯誤;這種處理方法雖然邏輯直觀,但是多層調用時需逐層傳遞且無法自動清理資源,功能有限。

所以,Bjarne Stroustrup在設計C++時,為了更好地處理程序中的錯誤,將異常處理機制引入了C++,其基本思想是讓函數在發現自己無法處理的錯誤時拋出一個異常,然後由其調用者來處理這個問題 。

一.異常概念:

C++異常時專門處理程序運行時錯誤的機制,核心是將“錯誤檢測”和“錯誤處理”分離。

核心語法

try-catch-throw

  • throw:“拋出異常”,在檢測到錯誤的地方使用,可拋出任意類型(如int、字符串、自定義對象)
  • try:將可能拋出異常的代碼塊放在 try 內部,標記“這段代碼需要監控異常” 
  • catch:“捕獲並處理異常”緊跟 try 之後,指定要處理的異常類型,只有類型匹配時才會執行(通常有多個catch)
// 舉例:模擬可能出錯的函數(參數非法時拋異常)
void checkAge(int age)
{
    if (age < 0 || age > 150) 
    {
        // throw:拋出異常(可拋int、字符串等任意類型)
        throw "年齡必須在 0-150 之間"; 
    }
    cout << "年齡合法:" << age << endl;
}

int main()
{
    // try:包裹「可能拋出異常」的代碼塊
    try 
    {
        checkAge(200); // 調用風險函數,會觸發異常
    }
    // catch:捕獲並處理異常,括號內指定要處理的異常類型
    //如果throw出的異常為int類型,這個catch無法捕捉
    catch (const char* errMsg) 
    { 
        cout << "捕獲異常:" << errMsg << endl; // 處理邏輯(如打印錯誤)
    }

    return 0;
}

catch匹配機制相關細節

  • 異常由拋出對象throw引發(可拋出任意類型),處理調用鏈中異常,會匹配離throw最近的一個;一旦throw出現,會跨越函數棧,直接跳到(最近的)catch

[C++]異常處理機制_異常類型

  • 對於catch它只能捕獲其參數對應類型異常,想捕獲任意類型異常,我們可以使用catch(...)(但它無法得知具體問題所以catch(...)一般放在最後防止拋不規範異常等問題) ;異常類型是從上往下識別,上面都沒被識別後走catch(...)
void func()
{
 //
  try
  {
    test();
  }
  catch(int err){
    cout<<"int"<<endl;}
  catch(const char* err){
    cout<<"char"<<endl;}
  catch(...){
    cout<<"不規範異常"<<endl;}
}
  • 但在實際應用中,拋出和捕獲的匹配原則存在例外,並不都是絕對相同類型的匹配:利用多態特性拋出派生類對象,使用基類捕獲
// 1. 異常基類(假設異常類是水果類)
class BaseException {};

// 2. 派生類(蘋果、香蕉,都是水果的子類)
class AppleException : public BaseException {};
class BananaException : public BaseException {};

// 3. 拋出派生類異常(扔蘋果/香蕉)
void doSomething() {
  if (/* 某種錯誤 */) throw AppleException();  // 扔蘋果
  if (/* 另種錯誤 */) throw BananaException(); // 扔香蕉
}

// 4. 用基類捕獲(只抓“水果”,就接住了蘋果和香蕉)
int main() {
  try {
    doSomething();
  }
  // 關鍵:抓基類,就能接住所有派生類異常
  catch (const BaseException& e) {
    //可以利用多態,不同子類對象產生不同效果
    cout << "抓住了異常(不管是蘋果還是香蕉)" << endl;
  }
  return 0;
}

異常重新拋出

異常重新拋出是在 catch 塊捕獲異常後,不徹底處理,而是再次拋出異常給外層代碼處理的操作。

  1. 拋原異常:用  throw; (無參數)拋出當前捕獲的異常,保留原始信息(類型、錯誤描述),適用於“內層記錄日誌,外層最終處理”的場景。
void test(){
    throw runtime_error("文件讀寫失敗"); // 拋出原始異常 
}
void func() {
    try {
        test();
    }
    // 內層catch:僅記錄日誌,不徹底處理
    catch (const runtime_error& e) {
        cout << "[內層] 記錄異常日誌:" << e.what() << endl;
        throw; // 關鍵:無參數,拋出原異常給外層
    }
}

int main() {
    try {
         func(); // 觸發異常
    }
    // 外層catch:最終處理原異常
    catch (const runtime_error& e) {
        cout << "[外層] 最終處理:" << e.what() << ",已自動重試文件操作" << endl;
    }
    return 0;
}
  1.  拋新異常:用  throw 新異常  拋出不同異常(可基於原異常補充信息),適用於“轉換異常類型,適配外層處理邏輯”的場景。
void dbOperate() {
    // 內層拋出數據庫專屬異常
    throw invalid_argument("數據庫賬號密碼錯誤");
}
int main() {
    try 
    {
        //第二個個try可以分函數,也可以寫在第一個try裏面
        try 
        {
            dbOperate(); // 觸發數據庫異常
        }
        // 內層catch:將數據庫異常轉為通用業務異常
        catch (const invalid_argument& e) 
        {
            // 拋新異常,補充業務場景説明
            throw logic_error("業務初始化失敗:" + string(e.what()));
        }
    }
    // 外層catch:處理通用業務異常
    catch (const logic_error& e) {
        cout << "[業務層] 處理錯誤:" << e.what() << ",已提示用户檢查配置" << endl;
    }
    return 0;
}

補充:runtime_error和invalid_argument是C++標準庫提供的標準異常類,屬於std命名空間,定義在頭文件<stdexcpet>

二.異常規範

異常規範是一種聲明函數可能拋出哪些類型異常的語法

異常規範的三種形式

1.動態異常規範(C++98/03)
void func1();//可拋出任意類型異常,默認
void func2()throw(int,double);//拋int,double類型異常
void func3()throw();//不拋異常
void func4()throw(...);//可拋出任意類型異常

目的是讓函數明確聲明“只拋出這幾種異常”,能輕易看出可能出什麼錯誤,且在違反了“承諾”(拋出異常不是聲明的)時,運行崩潰。

但是:這種設計存在問題:①難以維護 ②模板無法聲明具體類型 ③性能開銷以及其他問題無法解決;

所以動態異常規範在C++11年開始標記為廢棄,C++17完全移除(編譯錯誤),C++20徹底消失。

2.noexcept

當動態異常規範被捨棄,C++11引入了新的異常規範(説明符) noexcept;只表明兩種狀態:可能拋異常,不拋異常

void func1()noexcept;//絕對不拋異常
void func2() noexcept(false);//可能拋異常

核心用途 

1. 提升性能:編譯器已知函數不拋異常,可省略異常處理相關的冗餘代碼(如棧展開準備),優化執行效率。

2. 明確接口契約:讓調用者清晰知道“此函數無需處理異常”,減少不必要的  try-catch ,簡化代碼。

3. 影響標準庫行為:部分標準庫容器(如 vector)會根據函數是否  noexcept ,選擇更高效的實現(如移動操作)。

3.無規範
void func();//可能拋異常,正常寫就行

三.異常庫

C++ 中與異常相關的“庫”,核心是  <exception>  和  <stdexcept>  兩個頭文件,提供異常處理的基礎類和常用具體異常類型,支撐整個異常機制。(這部分不是很常用,需要查找就可以)

 頭文件<exception>  
  • 功能:定義所有標準異常的基類 std::exception 
  • 關鍵成員: virtual const char* what() const noexcept ,返回異常描述信息(子類可重寫)。
頭文件 <stdexcept>  
  • - 功能:定義常用具體異常類,均繼承自 std::exception ,覆蓋多數場景。
  • - 常見類型(分兩類):
  • - 邏輯錯誤(編譯可預判): std::invalid_argument (無效參數)、 std::out_of_range (越界)、 std::logic_error (基類)。
  • - 運行時錯誤(運行中出現): std::runtime_error (基類)、 std::overflow_error (算術溢出)、 std::underflow_error (算術下溢)。
其他關聯頭文件
  • new:定義  std::bad_alloc (內存分配失敗,如  new  失敗),繼承自  std::exception 。
  • typeinfo:定義  std::bad_cast (非法類型轉換,如  dynamic_cast  失敗),繼承自  std::exception 。

四.自定義異常體系

C++ 自定義異常體系,核心是讓自定義異常類繼承標準異常類(如  std::exception  或其子類),既符合標準用法,又能靈活擴展自己的異常信息

舉個例子:簡單模擬一下服務器開發中通常使用的異常繼承體系

class Exception//基類
{
public:
	Exception(const string& errmsg, int id)
		:_errmsg(errmsg)
		, _id(id)
	{}
	virtual string what() const
	{
		return _errmsg;
	}
protected:
	string _errmsg;
	int _id;
};

//各子類繼承基類
class SqlException : public Exception
{
public:
	SqlException(const string& errmsg, int id, const string& sql)
		:Exception(errmsg, id)
		, _sql(sql)
	{}
	virtual string what() const//重寫
	{
		string str = "SqlException:";
		str += _errmsg;
		str += "->";
		str += _sql;
		return str;
	}
private:
	const string _sql;
};

class CacheException : public Exception
{
public:
	CacheException(const string& errmsg, int id)
		:Exception(errmsg, id)
	{}
	virtual string what() const
	{
		string str = "CacheException:";
		str += _errmsg;
		return str;
	}
};
class HttpServerException : public Exception
{
public:
	HttpServerException(const string& errmsg, int id, const string& type)
		:Exception(errmsg, id)
		, _type(type)
	{}
	virtual string what() const
	{
		string str = "HttpServerException:";
		str += _type;
		str += ":";
		str += _errmsg;
		return str;
	}
private:
	const string _type;
};

//測試
void SQLMgr()
{
	srand(time(0));
	if (rand() % 7 == 0)
	{
		throw SqlException("權限不足", 100, "select * from name = '張三'");
	}
	//throw "xxxxxx";
}

void CacheMgr()
{
	srand(time(0));
	if (rand() % 5 == 0)
	{
		throw CacheException("權限不足", 100);
	}
	else if (rand() % 6 == 0)
	{
		throw CacheException("數據不存在", 101);
	}
	SQLMgr();
}

void HttpServer()
{
	// ...
	srand(time(0));
	if (rand() % 3 == 0)
	{
		throw HttpServerException("請求資源不存在", 100, "get");
	}
	else if (rand() % 4 == 0)
	{
		throw HttpServerException("權限不足", 101, "post");
	}
	CacheMgr();
} 
int main()
{
	//while (1)
	//{
		//Sleep(500);
		try {
			HttpServer();
		}
		catch (const Exception& e) // 這裏捕獲父類對象就可以
		{
			// 多態
			cout << e.what() << endl;
		}
		catch (...)
		{
			cout << "Unkown Exception" << endl;
		}
		cout << "執行成功" << endl;
	//}
	return 0;
}

五.異常體系優缺點

優點

  • 能夠清晰展現錯誤信息,定位bug
  • 不用層層返回
  • 更好兼容第三方庫
  • 處理越界

缺點

  • 執行會直接跳出函數棧,不好調試
  • 性能開銷
  • C++沒有垃圾回收機制,可能造成內存泄漏,鎖死(RAII)等問題
  • 異常規範使用沒有強制要求(早起throw()規範設計僵化,即使用noexcept優化,但需要兼容舊代碼,不能強制設置規範)

六.異常安全

異常安全指代碼在拋出/處理異常時,仍能保證資源不泄露、數據狀態合法、程序可正常恢復的特性,按安全等級從低到高分為3類:

  • 基本保證:異常後,數據狀態合法(無破壞),但具體值不確定;
  • 強保證:異常後,程序狀態完全回退到異常發生前(要麼操作全成,要麼全不做);
  • 無拋保證(noexcept):函數絕對不拋出異常,常見於析構(可能需要釋放內存,拋異常會引發程序終止,導致內存泄漏)、簡單訪問接口。

- 異常處理:是“操作手段”,指用  try/catch/throw  等語法捕獲、拋出、處理異常的具體過程,解決“異常發生時如何響應”的問題。

- 異常安全:是“質量目標”,指代碼在異常處理過程中,仍能保證資源不泄露、數據狀態合法的特性,解決“異常發生後程序如何保持可靠”的問題。