?前言
? 早在c語言的時候,就已經有處理錯誤的方式了,第一種方式太過暴力,就是斷言,程序發生錯誤,直接終止退出,這樣的報錯對于真正開發應用等太過暴力。第二種方式,就是返回errno,其實,我們會在發生錯誤的時候,或者程序結束運行的時候,返回一個錯誤碼,錯誤碼就是一個數字,不同的數字的錯誤碼表示的錯誤信息也不一樣,c語言種就是用errno表示這些錯誤碼。
但這兩種方式在處理錯誤上不是比較好,因此c++就引入了異常,用來處理錯誤。
異常
異常的拋出與捕獲機制
未知異常
拋子類異常捕獲父類異常
?異常的重新拋出
異常的安全問題
異常的規范
異常
throw: 當問題出現時,程序會拋出一個異常。這是通過使用 throw 關鍵字來完成的。catch: 在您想要處理問題的地方,通過異常處理程序捕獲異常 . catch 關鍵字用于捕獲異常,可以有多個 catch 進行捕獲。try: try 塊中的代碼標識將被激活的特定異常 , 它后面通常跟著一個或多個 catch 塊。如果有一個塊拋出一個異常,捕獲異常的方法會使用 try 和 catch 關鍵字。 try 塊中放置可能拋 出異常的代碼,try 塊中的代碼被稱為保護代碼。
例如下面的簡單例子:
double Divsion(int x, int y)
{if (y == 0){//如果分母為零,我們就可以拋異常,這里拋得東西可以是任意類型的對象throw "除零錯誤";}else{return (double)x / y;}}
int main()
{try{int x = 1.1; int y = 0;double ret = Divsion(x,y);}catch (const char * x) //捕獲異常,并打印異常的錯誤信息{cout << x<<endl;}return 0;
}
注意:1.拋異常可以是任意類型的對象,不捕獲異常也會報錯(無具體的錯誤信息).2.可以在任意地方捕獲異常(任何一個函數棧幀),一般是在調用的哪里捕獲。
重點? ? ? ?3.捕獲異常的類型必須與拋出異常的類型一致,否則無法捕獲。
程序在運行的過程中在遇到這里的異常后會立馬就會跑到捕獲異常的代碼處,捕獲之后正常繼續執行, 若沒拋異常則會跳過try catch語句。
異常的拋出與捕獲機制
1. 異常是通過拋出對象而引發的,該對象的類型決定了應該激活哪個catch的處理代碼。2. 被選中的處理代碼是調用鏈中與該對象類型匹配且離拋出異常位置最近的那一個。3. 拋出異常對象后,會生成一個異常對象的拷貝,因為拋出的異常對象可能是一個臨時對象,所以會生成一個拷貝對象,這個拷貝的臨時對象會在被catch以后銷毀。(這里的處理類似于函數的傳值返回)4. catch(...)可以捕獲任意類型的異常,問題是不知道異常錯誤是什么。5. 實際中拋出和捕獲的匹配原則有個例外,并不都是類型完全匹配,可以拋出的派生類對象, 使用基類捕獲,這個在實際中非常實用。
?
1. 首先檢查throw本身是否在try塊內部,如果是再查找匹配的catch語句。如果有匹配的,則調到catch的地方進行處理。2. 沒有匹配的catch則退出當前函數棧,繼續在調用函數的棧中進行查找匹配的catch。3. 如果到達main函數的棧,依舊沒有匹配的,則終止程序。上述這個沿著調用鏈查找匹配的catch子句的過程稱為棧展開。所以實際中我們最后都要加一個catch(...)捕獲任意類型的異常,否則當有異常沒捕獲,程序就會直接終止。4. 找到匹配的catch子句并處理以后,會繼續沿著catch子句后面繼續執行。
?例如:
class Func
{
public:void test(){cout << "Func for test";}
};
double Divsion(int x, int y)
{if (y == 0){//如果分母為零,我們就可以拋異常,這里拋得東西可以是任意類型的對象throw "除零錯誤";}else{return (double)x / y;}//若有拋出異常,則在捕獲異常時Func a;
}
void Test()
{Func a;//在捕獲時,優先走棧鏈最近的哪一個,這里就走test里的,而不走main中的try{int x = 1.1; int y = 0;double ret = Divsion(x, y);}catch (const char* x){cout << x << endl;}a.test();
};
int main()
{Test();try{int x = 1.1; int y = 0;double ret = Divsion(x,y);}catch (const char * x){cout << x<<endl;}//如果都走到mian這里,且走完了,也沒有發現有捕獲的,依然會報錯return 0;
}
綜上可得:異常的捕獲會引發執行流的跳躍。
?在拋出異常時,會自動生成拋出異常的臨時拷貝,到了捕獲時,傳給他。
未知異常
double Division(int a, int b)
{// 當b == 0時拋出異常if (b == 0){throw "Division by zero condition!";} elsereturn ((double)a / (double)b);
}
void Test()
{throw string("test");
}
void Func()
{int len, time;cin >> len >> time;cout << Division(len, time) << endl;
}
int main()
{try {Func();}catch (const char* errmsg){cout << errmsg << endl;}try{Test();}catch (...) {cout << "unkown exception" << endl;}return 0;
}
拋子類異常捕獲父類異常
實際服務器開發中的異常體系,首先會寫一個父類,通常包含異常信息message和異常id,之后,在對于運行庫,寄存器,協議的調用中,對應寫一個子類,重寫里面的錯誤信息,設置好異常捕捉,在捕捉時,直接通過父類對象異常即可。
//父類異常
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(10);try {HttpServer();}catch (const Exception& e) // 這里捕獲父類對象就可以{// 多態cout << e.what() << endl;}catch (...){cout << "Unkown Exception" << endl;}}return 0;
}
?異常的重新拋出
void Func()
{throw string("test");
}
void Test()
{//假設在此之前我們有一個數組int* arry = new int[10];Func();delete[]arry;arry = nullptr;cout << "arry has reliszed";
}
int main()
{try{Test();}catch (string str){cout << str << endl;}
}
void Func()
{throw string("test");
}
void Test()
{//假設在此之前我們有一個數組int* arry = new int[10];//在該位置,再次進行捕捉,并拋出try{Func();}catch (string str){cout << str << endl;delete[]arry;arry = nullptr;cout << "arry has reliszed";throw;}}
int main()
{try{Test();}catch (string str){cout << str << endl;}
}
異常的安全問題
異常的規范
對于需要拋異常的,我們在函數后面列出可能會拋出的類型。
對于不用拋異常的,c++提供了關鍵字noexcept,在函數體后面添加noexcept表示不會有異常,但我們要確保沒有異常。
若函數體沒有任何聲明,則可能會拋出各種異常。
事實上在這里聲明可能會拋出的異常的類型,并不是很準確的,我們聲明只有一種類型,但也可以拋別的異常,不過c++規范我們去使用異常,確信會拋出哪些異常,我們就在函數后面聲明。
// 這里表示這個函數會拋出列出中的某種類型的異常
void fun() throw(A,B,C,D);// 這里表示這個函數只會拋出bad_alloc的異常
void* operator new (std::size_t size) throw (std::bad_alloc);// C++11 中新增的noexcept,表示不會拋異常
thread() noexcept;
thread (thread&& x) noexcept;
異常的使用有缺點也有優點,但運用起來利大于弊。