C++異常處理方式實例詳解(超級詳細!)
1.C語言傳統(tǒng)的處理錯誤的方式
傳統(tǒng)的錯誤處理機制:
1. 終止程序,如assert,缺陷:用戶難以接受。如發(fā)生內(nèi)存錯誤,除0錯誤時就會終止程序。
2. 返回錯誤碼,缺陷:需要程序員自己去查找對應(yīng)的錯誤。如系統(tǒng)的很多庫的接口函數(shù)都是通過把錯誤碼放到errno中,表示錯誤實際中C語言基本都是使用返回錯誤碼的方式處理錯誤,部分情況下使用終止程序處理非常嚴(yán)重的錯誤。
2. C++異常概念
異常是一種處理錯誤的方式,當(dāng)一個函數(shù)發(fā)現(xiàn)自己無法處理的錯誤時就可以拋出異常,讓函數(shù)的直接或間接的調(diào)用者處理這個錯誤。
throw: 當(dāng)問題出現(xiàn)時,程序會拋出一個異常。這是通過使用 throw 關(guān)鍵字來完成的。
catch: 在您想要處理問題的地方,通過異常處理程序捕獲異常.catch 關(guān)鍵字用于捕獲異常,可以有多個catch進行捕獲。
try: try 塊中的代碼標(biāo)識將被激活的特定異常,它后面通常跟著一個或多個 catch 塊。
如果有一個塊拋出一個異常,捕獲異常的方法會使用 try 和 catch 關(guān)鍵字。try 塊中放置可能拋出異常的代碼,try 塊中的代碼被稱為保護代碼。使用 try/catch 語句的語法如下所示:
try
{
// 保護的標(biāo)識代碼
}
catch (ExceptionName e1)
{
// catch 塊
}
catch (ExceptionName e2)
{
// catch 塊
}
catch (ExceptionName eN)
{
// catch 塊
}3. 異常的使用
3.1 異常的拋出和捕獲
異常的拋出和匹配原則
1. 異常是通過拋出對象而引發(fā)的,該對象的類型決定了應(yīng)該激活哪個catch的處理代碼。
2. 被選中的處理代碼是調(diào)用鏈中與該對象類型匹配且離拋出異常位置最近的那一個。
3. 拋出異常對象后,會生成一個異常對象的拷貝,因為拋出的異常對象可能是一個臨時對象,所以會生成一個拷貝對象,這個拷貝的臨時對象會在被catch以后銷毀。(這里的處理類似于函數(shù)的傳值返回)
4. catch(...)可以捕獲任意類型的異常,問題是不知道異常錯誤是什么。
5. 實際中拋出和捕獲的匹配原則有個例外,并不都是類型完全匹配,可以拋出的派生類對象,使用基類捕獲,這個在實際中非常實用。
在函數(shù)調(diào)用鏈中異常棧展開匹配原則
1. 首先檢查throw本身是否在try塊內(nèi)部,如果是再查找匹配的catch語句。如果有匹配的,則調(diào)到catch的地方進行處理。
2. 沒有匹配的catch則退出當(dāng)前函數(shù)棧,繼續(xù)在調(diào)用函數(shù)的棧中進行查找匹配的catch。
3. 如果到達main函數(shù)的棧,依舊沒有匹配的,則終止程序。上述這個沿著調(diào)用鏈查找匹配的catch子句的過程稱為棧展開。所以實際中我們最后都要加一個catch(...)捕獲任意類型的異常,否則當(dāng)有異常沒捕獲,程序就會直接終止。
4. 找到匹配的catch子句并處理以后,會繼續(xù)沿著catch子句后面繼續(xù)執(zhí)行。

double Division(int a, int b)
{
// 當(dāng)b == 0時拋出異常
if (b == 0)
throw "Division by zero condition!";
else
return ((double)a / (double)b);
}
void Func()
{
int len, time;
cin >> len >> time;
cout << Division(len, time) << endl;
}
int main()
{
try
{
Func();
}
catch (const char* errmsg)
{
cout << errmsg << endl;
}
catch (...)
{
cout << "unkown exception" << endl;
}
return 0;
}運行截圖:

3.2 異常的重新拋出
有可能單個的catch不能完全處理一個異常,在進行一些校正處理以后,希望再交給更外層的調(diào)用鏈函數(shù)來處理,catch則可以通過重新拋出將異常傳遞給更上層的函數(shù)進行處理。
double Division(int a, int b)
{
// 當(dāng)b == 0時拋出異常
if (b == 0)
{
throw "Division by zero condition!";
}
return (double)a / (double)b;
}
void Func()
{
// 這里可以看到如果發(fā)生除0錯誤拋出異常,另外下面的array沒有得到釋放。
// 所以這里捕獲異常后并不處理異常,異常還是交給外面處理,這里捕獲了再
// 重新拋出去。
int* array = new int[10];
try
{
int len, time;
cin >> len >> time;
cout << Division(len, time) << endl;
}
catch (...)
{
cout << "delete []" << array << endl;
delete[] array;
throw;
}
// ...
cout << "delete []" << array << endl;
delete[] array;
}
int main()
{
try
{
Func();
}
catch (const char* errmsg)
{
cout << errmsg << endl;
}
return 0;
}運行截圖:

3.3異常安全
構(gòu)造函數(shù)完成對象的構(gòu)造和初始化,最好不要在構(gòu)造函數(shù)中拋出異常,否則可能導(dǎo)致對象不完整或沒有完全初始化析構(gòu)函數(shù)主要完成資源的清理,最好不要在析構(gòu)函數(shù)內(nèi)拋出異常,否則可能導(dǎo)致資源泄漏(內(nèi)存泄漏、句柄未關(guān)閉等)
C++中異常經(jīng)常會導(dǎo)致資源泄漏的問題,比如在new和delete中拋出了異常,導(dǎo)致內(nèi)存泄漏,在lock和unlock之間拋出了異常導(dǎo)致死鎖,C++經(jīng)常使用RAII來解決以上問題.
3.4 異常規(guī)范
1. 異常規(guī)格說明的目的是為了讓函數(shù)使用者知道該函數(shù)可能拋出的異常有哪些。 可以在函數(shù)的后面接throw(類型),列出這個函數(shù)可能拋擲的所有異常類型。
2. 函數(shù)的后面接throw(),表示函數(shù)不拋異常。
3. 若無異常接口聲明,則此函數(shù)可以拋擲任何類型的異常。
// 這里表示這個函數(shù)會拋出A/B/C/D中的某種類型的異常 void fun() throw(A,B,C,D); // 這里表示這個函數(shù)只會拋出bad_alloc的異常 void* operator new (std::size_t size) throw (std::bad_alloc); // 這里表示這個函數(shù)不會拋出異常 void* operator delete (std::size_t size, void* ptr) throw(); // C++11 中新增的noexcept,表示不會拋異常 thread() noexcept; thread (thread&& x) noexcept;
4.自定義異常體系
實際使用中很多公司都會自定義自己的異常體系進行規(guī)范的異常管理,因為一個項目中如果大家隨意拋異常,那么外層的調(diào)用者基本就沒辦法玩了,所以實際中都會定義一套繼承的規(guī)范體系。
這樣大家拋出的都是繼承的派生類對象,捕獲一個基類就可以了

// 服務(wù)器開發(fā)中通常使用的異常繼承體系
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("權(quán)限不足", 100, "select * from name = '張三'");
}
//throw "xxxxxx";
}
void CacheMgr()
{
srand(time(0));
if (rand() % 5 == 0)
{
throw CacheException("權(quán)限不足", 100);
}
else if (rand() % 6 == 0)
{
throw CacheException("數(shù)據(jù)不存在", 101);
}
SQLMgr();
}
void HttpServer()
{
// ...
srand(time(0));
if (rand() % 3 == 0)
{
throw HttpServerException("請求資源不存在", 100, "get");
}
else if (rand() % 4 == 0)
{
throw HttpServerException("權(quán)限不足", 101, "post");
}
CacheMgr();
}
int main()
{
while (1)
{
try
{
HttpServer();
}
catch (const Exception& e) // 這里捕獲父類對象就可以
{
// 多態(tài)
cout << e.what() << endl;
Sleep(1000);
}
catch (...)
{
cout << "Unkown Exception" << endl;
}
}
return 0;
}運行截圖:

5.C++標(biāo)準(zhǔn)庫的異常體系
C++ 提供了一系列標(biāo)準(zhǔn)的異常,定義在exception中,我們可以在程序中使用這些標(biāo)準(zhǔn)的異常。它們是以父子類層次結(jié)構(gòu)組織起來的,如下所示:


說明:實際中我們可以可以去繼承exception類實現(xiàn)自己的異常類。但是實際中很多公司像上面一
樣自己定義一套異常繼承體系。因為C++標(biāo)準(zhǔn)庫設(shè)計的不夠好用。
int main()
{
try {
vector<int> v(10, 5);
// 這里如果系統(tǒng)內(nèi)存不夠也會拋異常
v.reserve(1000000000);
// 這里越界會拋異常
v.at(10) = 100;
}
catch (const exception& e) // 這里捕獲父類對象就可以
{
cout << e.what() << endl;
}
catch (...)
{
cout << "Unkown Exception" << endl;
}
return 0;
}6.異常的優(yōu)缺點
C++異常的優(yōu)點:
1. 異常對象定義好了,相比錯誤碼的方式可以清晰準(zhǔn)確的展示出錯誤的各種信息,甚至可以包含堆棧調(diào)用的信息,這樣可以幫助更好的定位程序的bug。
2. 返回錯誤碼的傳統(tǒng)方式有個很大的問題就是,在函數(shù)調(diào)用鏈中,深層的函數(shù)返回了錯誤,那么我們得層層返回錯誤,最外層才能拿到錯誤,具體看下面的詳細解釋。
// 1.下面這段偽代碼我們可以看到ConnnectSql中出錯了,先返回給ServerStart,
ServerStart再返回給main函數(shù),main函數(shù)再針對問題處理具體的錯誤。
// 2.如果是異常體系,不管是ConnnectSql還是ServerStart及調(diào)用函數(shù)出錯,都不用檢查,因
為拋出的異常異常會直接跳到main函數(shù)中catch捕獲的地方,main函數(shù)直接處理錯誤。
int ConnnectSql()
{
// 用戶名密碼錯誤
if (...)
return 1;
// 權(quán)限不足
if (...)
return 2;
}
int ServerStart() {
if (int ret = ConnnectSql() < 0)
return ret;
int fd = socket()
if(fd < 0)
return errno;
}
int main()
{
if(ServerStart()<0)
...
return 0;
}3. 很多的第三方庫都包含異常,比如boost、gtest、gmock等等常用的庫,那么我們使用它們也需要使用異常。
4. 部分函數(shù)使用異常更好處理,比如構(gòu)造函數(shù)沒有返回值,不方便使用錯誤碼方式處理。比如T& operator這樣的函數(shù),如果pos越界了只能使用異?;蛘呓K止程序處理,沒辦法通過返回值表示錯誤。
C++異常的缺點:
1. 異常會導(dǎo)致程序的執(zhí)行流亂跳,并且非常的混亂,并且是運行時出錯拋異常就會亂跳。這會導(dǎo)致我們跟蹤調(diào)試時以及分析程序時,比較困難。
2. 異常會有一些性能的開銷。當(dāng)然在現(xiàn)代硬件速度很快的情況下,這個影響基本忽略不計。
3. C++沒有垃圾回收機制,資源需要自己管理。有了異常非常容易導(dǎo)致內(nèi)存泄漏、死鎖等異常安全問題。這個需要使用RAII來處理資源的管理問題。學(xué)習(xí)成本較高。
4. C++標(biāo)準(zhǔn)庫的異常體系定義得不好,導(dǎo)致大家各自定義各自的異常體系,非常的混亂。
5. 異常盡量規(guī)范使用,否則后果不堪設(shè)想,隨意拋異常,外層捕獲的用戶苦不堪言。所以異常規(guī)范有兩點:一、拋出異常類型都繼承自一個基類。
二、函數(shù)是否拋異常、拋什么異常,都使用 func()throw();的方式規(guī)范化。
總結(jié):異??傮w而言,利大于弊,所以工程中我們還是鼓勵使用異常的。另外其它的語言基本都是用異常處理錯誤,這也可以看出這是大勢所趨。
總結(jié)
到此這篇關(guān)于C++異常處理方式的文章就介紹到這了,更多相關(guān)C++異常處理方式內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
深入理解:Java是類型安全的語言,而C++是非類型安全的語言
本篇文章是對Java是類型安全的語言,而C++是非類型安全的語言進行了詳細的分析介紹,需要的朋友參考下2013-06-06
c++獲取sqlite3數(shù)據(jù)庫表中所有字段的方法小結(jié)
本文給大家分享c++獲取sqlite3數(shù)據(jù)庫表中所有字段的三種常用方法,本文針對每一種方法給大家詳細介紹,需要的的朋友通過本文一起學(xué)習(xí)吧2016-11-11
OpenCV圖像特征提取之Shi-Tomasi角點檢測算法詳解
Harris角點檢測算法就是對角點響應(yīng)函數(shù)R進行閾值處理,Shi-Tomasi原理幾乎和Harris一樣的,只不過最后計算角點響應(yīng)的公式發(fā)生了變化。本文將和大家詳細說說Shi-Tomasi角點檢測算法的原理與實現(xiàn),需要的可以參考一下2022-09-09
Cocos2d-x學(xué)習(xí)入門之HelloWorld程序
這篇文章主要介紹了Cocos2d-x學(xué)習(xí)入門之HelloWorld程序,是學(xué)習(xí)Cocos2d-x的入門程序,其重要性不言而喻,需要的朋友可以參考下2014-08-08

