欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

深度剖析C++中的異常機制

 更新時間:2023年07月30日 08:53:02   作者:linux大本營  
異常是面向對象語言常用的一種處理錯誤的方式,當一個函數(shù)發(fā)現(xiàn)自己無法處理的錯誤時就可以拋出異常,本文我們將對C++ 異常機制進行深入剖析,感興趣的同學跟著小編一起來看看吧

傳統(tǒng)排錯

我們早在 C 程序里面?zhèn)鹘y(tǒng)的錯誤處理手段有:

  1. 終止程序,如 assert;缺陷是用戶難以接受,說白了就是一種及其粗暴的手法,比如發(fā)生內存錯誤,除0錯誤時就會終止程序。

  2. 返回錯誤碼。缺陷是需要我們自己去查找錯誤,如系統(tǒng)的很多庫的接口函數(shù)都是通過把錯誤碼放到 errno 中,表示錯誤。

  3. C標準庫中 setjmp 和 longjmp 組合(不常用)

實際中 C 語言基本都是使用返回錯誤碼的方式處理錯誤,部分情況下使用終止程序處理非常嚴重緊急的錯誤,因此異常機制就時運而橫空出世

概念

異常是面向對象語言常用的一種處理錯誤的方式,當一個函數(shù)發(fā)現(xiàn)自己無法處理的錯誤時就可以拋出異常,讓函數(shù)直接或間接調用者自己來處理這個錯誤

  1. throw:當程序出現(xiàn)問題時,可以通過 throw 關鍵字拋出一個異常

  2. try:try 塊中放置的是可能拋出異常的代碼,該代碼塊在執(zhí)行時將進行異常錯誤檢測,try 塊后面通常跟著一個或多個 catch 塊。

  3. catch:如果try塊中發(fā)生錯誤,則可以在 catch 塊中定義對應要執(zhí)行的代碼塊。

try-catch 語句的語法實例:

try
{
	//被保護的代碼
}
catch (ExceptionName e1)
{
	//catch塊
}
catch (ExceptionName e2)
{
	//catch塊
}
catch (ExceptionName eN)
{
	//catch塊
}

用法

異常是通過拋出對象而引發(fā)的,該對象的類型決定了應該激活哪個 catch 的處理代碼,如果拋出的異常對象沒有捕獲,或是沒有匹配類型的捕獲,那么程序會終止報錯

異常捕獲和拋出

被選中的處理代碼(catch塊)是調用鏈中與該對象類型匹配且離拋出異常位置最近的那一個

拋出異常對象后,會生成一個異常對象的拷貝,因為拋出的異常對象可能是一個臨時對象,所以會生成一個拷貝對象 \color{red} {因為拋出的異常對象可能是一個臨時對象,所以會生成一個拷貝對象}因為拋出的異常對象可能是一個臨時對象,所以會生成一個拷貝對象,這個拷貝的臨時對象會在被 catch 以后銷毀(類似于函數(shù)的傳值返回)

catch(…) 可以捕獲任意類型的異常,但捕獲后無法知道異常錯誤是什么,實際異常拋出和捕獲的匹配原則有個例外,捕獲和拋出的異常類型并不一定要完全匹配,可以拋出派生類對象,使用基類進行捕獲,這個在實際中非常有用

在函數(shù)調用鏈中異常棧展開的匹配原則:

當異常被拋出后,首先檢查 throw 本身是否在 try 塊內部,如果在則查找匹配的 catch 語句,如果有匹配的就跳到 catch 的地方進行處理

如果當前沒有匹配的 catch 則退出當前函數(shù)棧,繼續(xù)在上一個調用中進行查找 catch。找到匹配的 catch 子句并處理以后,會沿著 catch 子句后面繼續(xù)執(zhí)行,而不會跳回到原來拋異常的地方,如果到達 main 函數(shù)的棧,依舊沒有找到匹配的 catch 則終止程序

比如下面的代碼中調用了 func3,func3 中調用 func2,func2 中調用 func1,func1 中拋出了一個 string 的異常對象:

void func1()
{
	throw string("這是一個異常");
}
void func2()
{
	func1();
}
void func3()
{
	func2();
}
int main()
{
	try
	{
		func3();
	}
	catch (const string& s)
	{
		cout << "錯誤描述:" << s << endl;
	}
	catch (...)
	{
		cout << "未知異常" << endl;
	}
	return 0;
}

首先會檢查 throw 本身是否在 try 塊內部,這里就會因此退出 func1 所在的函數(shù)棧,繼續(xù)在上一個調用棧中進行查找,即 func2 所在的函數(shù)棧,由于 func2 中也沒有匹配的 catch,因此會繼續(xù)復讀套娃,最終在 main 函數(shù)棧中找到匹配的 catch

這時就會跳到 main 函數(shù)中對應的 catch 塊中執(zhí)行對應的代碼塊,執(zhí)行完后繼續(xù)執(zhí)行該代碼塊后續(xù)的代碼:

當然為了防止還有漏網之魚,一般此時我們還會搞一個 catch(…) 進行全捕獲。

異常的重新拋出

要知道一個 catch 是無法完全搞定異常的,如果我們對異常進行修正后,希望交付給上層調用鏈進行異常的異常信息日志記錄,此時就需要我們重新對上層函數(shù)拋異常:

void func1()
{
	throw string("這是一個異常");
}
void func2()
{
	int* array = new int[10];
	func1();
	//省略函數(shù)對應實現(xiàn)
    //……
	delete[] array;
}
int main()
{
	try
	{
		func2();
	}
	catch (const string& s)
	{
		cout << s << endl;
	}
	catch (...)
	{
		cout << "未知異常" << endl;
	}
	return 0;
}

這里 func2 最后應該 delete 進行空間釋放,但由于 func2 中途調用 func1 ,func1 內部拋出了一個異常,這時會直接跳轉到 main 函數(shù)中的 catch 塊執(zhí)行對應的異常處理程序,并且在處理完后繼續(xù)沿著 catch 塊往后執(zhí)行,這時就導致 func2 中內存塊沒有得到釋放,造成了內存泄露!

此時我們應該在 func2 中先對 func1 拋出的異常進行捕獲,捕獲后先將內存釋放再重新拋出異常,就可以避免內存泄露:

void func2()
{
	int* array = new int[10];
	try
	{
		func1();
		//省略函數(shù)對應實現(xiàn)
        //……
	}
	catch (...)
	{
		delete[] array;
		throw; //將捕獲到的異常再次重新拋出
	}
	delete[] array;
}

try-catch 中 new 和 delete 之間可能還會拋出其他類型的異常,因此在 fun2 中最好再進行 catch(…) ,將申請到的內存 delete 后再通過throw 重新拋出;重新拋出異常對象時,此時 throw 可以不用指明要拋出的異常對象,其實 catch(…) 也不知道自己到底捕了個什么異常對象

安全第一條

還是那句話,道路千萬條,拋異常要謹慎:

  1. 構造函數(shù)完成對象的構造和初始化,最好不要在構造函數(shù)中拋出異常,否則可能導致對象不完整或沒有完全初始化

  2. 析構函數(shù)完成對象資源的清理,最好不要在析構函數(shù)中拋出異常,否則可能導致內存泄露,句柄未關閉等

  3. C++ 中在 new 和 delete 中拋出異常經常是內存泄漏的罪魁禍首,在 lock 和 unlock 之間拋出異常導致死鎖,C++ 經常使用 RAII 的方式來解決類似問題

規(guī)范使用

站在異常的嚴謹立場上, C++ 也在盡量提高咱的使用規(guī)范:

在函數(shù)的后面接throw(type1, type2, …),列出這個函數(shù)可能拋擲的所有異常類型 在函數(shù)的后面接throw()或noexcept(C++11),表示該函數(shù)不拋異常 若無異常接口聲明,則此函數(shù)可以拋擲任何類型的異常(異常接口聲明不是強制的)

//這里可能會拋出A/B/C/D類型的異常
void func() throw(A, B, C, D);
//這里只會拋出 bad_alloc 的異常
void* operator new(std::size_t size) throw(std::bad_alloc);
//這里不會拋出異常
void* operator new(std::size_t size, void* ptr) throw();

異常體系

因為異常屬實需要嚴謹與規(guī)范的操作,所以在很多公司里面都會制定自己的一套異常的規(guī)范管理:

公司中的項目一般會進行模塊劃分,讓不同的人或小組完成不同的模塊,如果不對拋異常這件事進行規(guī)范,那么在最外層捕獲異常的冤種就會問候親媽了,因為他會來給各位擦屁股,捕獲大家拋出的所以異常對象 \color{red} {那么在最外層捕獲異常的冤種就會問候親媽了,因為他會來給各位擦屁股,捕獲大家拋出的所以異常對象}那么在最外層捕獲異常的冤種就會問候親媽了,因為他會來給各位擦屁股,捕獲大家拋出的所以異常對象

我們之前說過異常語法可以用基類捕獲拋出的派生類對象,因此實際中都會先定義一個最基礎的異常類,所有人拋出的異常對象都必須是繼承于該異常類的派生類對象,,因此最外層就只需捕獲基類就行了

?最基礎的異常類至少需要包含錯誤編號和錯誤描述兩個成員變量,甚至還可以包含當前函數(shù)棧幀的調用鏈等信息,該異常類中一般還會提供兩個成員函數(shù),分別用來獲取錯誤編號和錯誤描述

class Exception
{
public:
	Exception(int errid, const char* errmsg)
		:_errid(errid)
		, _errmsg(errmsg)
	{}
	int GetErrid() const
	{
		return _errid;
	}
	virtual string what() const
	{
		return _errmsg;
	}
protected:
	int _errid;  //錯誤編號
	string _errmsg; //錯誤描述
	//...
};

其他人如果要對這個異常類進行擴展,必須先繼承基礎異常類,然后按需添加某些成員變量,或是對虛函數(shù)what 進行重寫,使其能告知更多的異常信息:

class CacheException : public Exception
{
public:
	CacheException(int errid, const char* errmsg)
		:Exception(errid, errmsg)
	{}
	virtual string what() const
	{
		string msg = "CacheException: ";
		msg += _errmsg;
		return msg;
	}
protected:
	//...
};
class SqlException : public Exception
{
public:
	SqlException(int errid, const char* errmsg, const char* sql)
		:Exception(errid, errmsg)
		, _sql(sql)
	{}
	virtual string what() const
	{
		string msg = "CacheException: ";
		msg += _errmsg;
		msg += "sql語句: ";
		msg += _sql;
		return msg;
	}
protected:
	string _sql; //異常的SQL語句
	//...
};

異常類的成員變量不能設置為私有,因為私有成員在子類中是不可見的?;?Exception 中 what 成員函數(shù)最好定義為虛函數(shù),方便子類對其進行重寫,從而達到多態(tài)的效果

標準庫體系

C++ 標準庫當中的異常也是一個基礎體系,其中 exception 就是基類,它與其他異常類的繼承關系如下:

?其中具體信息如下:

?我們可以去繼承這里的 exception 類來實現(xiàn)自己的異常類,但實際上很多公司都會自己定義一套異常繼承體系!

優(yōu)缺點

目前情況來看,異常是利大于弊的,還是鼓勵使用異常的,而且前排的語言基本都會使用異常處理錯誤,這也可以看出這是大勢所趨

異常的優(yōu)點:

相比錯誤碼,異??梢郧逦鷾蚀_的展示出錯誤的各種信息,甚至可以包含堆棧調用等信息,這樣可以幫助更好的定位程序的bug

返回錯誤碼的傳統(tǒng)方式有個很大的問題就是,在函數(shù)調用鏈中,深層的函數(shù)返回了錯誤,那么我們得層層返回錯誤碼,最終最外層才能拿到錯誤

很多的第三方庫都會使用異常,比如 boost、gtest、gmock 等常用的庫,如果我們不用異常就不能很好的發(fā)揮這些庫的作用,很多測試框架也都使用異常,因此使用異常能更好的使用單元測試等進行白盒測試

部分函數(shù)使用異常更好處理,比如 T& operator 這樣的函數(shù),如果 pos 越界了只能使用異?;蛘呓K止程序處理,沒辦法通過返回值表示錯誤

異常的缺點:

異常會導致程序的執(zhí)行流混亂,這會導致我們跟蹤調試或分析程序時比較困難。異常還會有一些性能的開銷,當然在現(xiàn)代硬件速度很快的情況下,這個影響基本忽略不計!

C++ 沒有垃圾回收機制,資源需要自己管理,有了異常非常容易導致內存泄露、死鎖等異常安全問題,這個需要使用 RAII 來處理資源的管理問題,學習成本比較高

C++ 標準庫的異常體系定義得不夠好,導致大家各自定義自己的異常體系,非常的混亂,異常盡量規(guī)范使用,否則后果不堪設想,隨意拋異常,也會讓外層捕獲的用戶苦不堪言。

異常接口聲明不是強制的,對于沒有聲明異常類型的函數(shù),無法預知該函數(shù)是否會拋出異常

以上就是深度剖析C++中的異常機制的詳細內容,更多關于C++ 異常機制的資料請關注腳本之家其它相關文章!

相關文章

  • c++中的前向聲明用法解讀

    c++中的前向聲明用法解讀

    這篇文章主要介紹了c++中的前向聲明用法解讀,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2023-06-06
  • C++多線程強制終止詳細

    C++多線程強制終止詳細

    這篇文章主要介紹了C++多線程強制終止, 實際上,沒有任何語言或操作系統(tǒng)可以為你提供異步突然終止線程的便利,且不會警告你不要使用它們。但是下面我們再來簡單看看相關內容吧
    2021-09-09
  • QT實現(xiàn)視頻傳輸功能

    QT實現(xiàn)視頻傳輸功能

    這篇文章主要為大家詳細介紹了QT實現(xiàn)視頻傳輸功能,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2022-08-08
  • C++?反匯編之關于Switch語句的優(yōu)化措施

    C++?反匯編之關于Switch語句的優(yōu)化措施

    這篇文章主要介紹了C++?反匯編之關于Switch語句的優(yōu)化措施,利用三種優(yōu)化來降低樹高度,誰的效率高就優(yōu)先使用誰,三種優(yōu)化都無法匹配才會使用判定樹,具體內容詳情跟隨小編一起看看吧
    2022-01-01
  • c++實現(xiàn)廣播通訊詳解

    c++實現(xiàn)廣播通訊詳解

    這篇文章主要為大家詳細介紹了c++實現(xiàn)廣播通訊的相關知識,文中的示例代碼講解詳細,具有一定的借鑒價值,有需要的小伙伴可以參考一下
    2024-12-12
  • C++類型兼容規(guī)則詳情

    C++類型兼容規(guī)則詳情

    這篇文章主要介紹了C++類型兼容規(guī)則詳情,共有繼承時,任何需要父類對象的地方,都能使用子類對象“替代”,這就是類型兼容規(guī)則,下面一起來了解文章相關內容吧
    2022-03-03
  • C語言中條件判斷的正確使用姿勢

    C語言中條件判斷的正確使用姿勢

    在C語言中,有三種條件判斷結構:if語句、if-else語句和switch語句,這篇文章主要來和大家講解一下它們的正確使用姿勢,需要的可以參考一下
    2023-05-05
  • C++面試八股文之std::string實現(xiàn)方法

    C++面試八股文之std::string實現(xiàn)方法

    這篇文章主要介紹了C++面試八股文:std::string是如何實現(xiàn)的,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2023-06-06
  • vscode+leetcode環(huán)境配置方法

    vscode+leetcode環(huán)境配置方法

    這篇文章主要介紹了vscode+leetcode環(huán)境配置,本文通過實例圖文相結合給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2020-03-03
  • C語言實現(xiàn)基于控制臺的電子時鐘

    C語言實現(xiàn)基于控制臺的電子時鐘

    這篇文章主要為大家詳細介紹了C語言實現(xiàn)基于控制臺的電子時鐘,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2022-05-05

最新評論