異常處理
本來覺得這章會教怎麼解決常見bug之類的,結果主要講的是跟蹤代碼中的異常並輸出,是講如何把程序寫得更加健壯。這些代碼畫風給人感覺就特別像Java
拋出異常
就是throw
這個命令,給出一個例子:
inline void Traingular_iterator::
check_integrity()
{
if (_index>=Triangular::_max_elems)
throw iterator_overflow(_index,Trinagular::_max_elems);
if (_index>=Triangular::_elems.size())
Triangular::gen_elements(_index+1);
};
//iterator_overflow爲一個類
class iterator_overflow{
public:
iterator_overflow(int index, int max)
: _index(index), _max(max){}
//...
};
只看這裏可能會感覺到有點奇怪,拋出以後呢?實際上拋出相當於給定了catch的輸入值。再看下一節
捕獲異常
還是先給例子:
bool some_function()
{
//...
catch(iterator_overflow &iof){
iof.what_happened(log_file);
status = false;
}
//...
}
這裏的catch
恰好輸入值爲前面throw
出來的結構體類型。當然catch
肯會不止一個,當出現多個catch
時。如果出現了throw
,程序會根據throw
的類型逐個比對是否符合catch
中的輸入。
另外catch
中可以嵌套throw
,而如果想要捕獲任何類型的異常,輸入部分也可以寫成省略號
catch(...)
{
log_message("exception of unknown type");
}
提煉異常
再引入try
的使用,前三節連在一起看就比較清晰了,先給出例子:
bool has_elem(Triangular_iterator first,
Triangular_iterator last, int elem)
{
bool status = true;
try
{
while(first!=last)
{
if(*first == elem)
return status;
++first;
}
}
catch(iterator_overflow &iof)
{
log_message(iof.what_happened());
log_message("check if iterators address same container");
}
status = false;
return status;
}
//前幾章定義的重載
inline int Triangular_iterator::
operator*()
{
check_integrity();
return Triangular::_elems[_index];
}
inline void Triangular_iterator::
check_integrity()
{
if (_index>=Triangular::_max_elems)
throw iterator_overflow(_index, Triangular::_max_elems);
//...
}
首先來解釋一下發生了什麼,has_elem
函數中try
了一段代碼,其中的*first
有可能出錯。而*
運算符已經被重載,重載的函數中的check_integrity()
會檢查程序的正確性。在check_integrity()
這個函數中如果出現了錯誤,就會throw
一個前一節定義的結構體iterator_overflow
。
程序的執行規則是如果出現異常拋出,首先判斷是否位於try
內,如果是,則判斷是否具有異常處理能力,即相對應的catch
。如果有,異常會被處理,程序會繼續執行。如果異常不在try
之內,則當前函數代碼段會直接跳出。
如果程序執行出現了錯誤,這個throw
真的被拋出。程序會在當前函數check_integrity()
中並非try
,但是很遺憾並不是,那麼程序就不會繼續向下執行,注意,是隻執行了一開始的if,下面沒有執行,函數check_integrity()
就直接跳出了。然後在*
操作符這裏,相當於收到了一個throw
,只不過來自它調用的函數,但是檢索發現這裏也沒有try
,那麼*
運算符中的
return Triangular::_elems[_index];
就不會執行。然後在調回最外層的函數has_elem()
,此時異常處於try
之內,並且恰好有catch
滿足要求,則運行catch
中的內容,然後繼續向下運行。
如果處於try
中,但是沒找到對應的catch
呢?仍然是跳出。如果一直回到main
函數,還是沒有找到合適的catch
呢?程序會調用標準庫中terminate()
,其執行結果是中斷程序。
至於try
和catch
以及throw
如何安排,是非常值得商榷的。文中進行了大段的討論,這裏不做敘述。
另外需要區別的是,C++的每個異常都可以找到對應的throw
。不過segmentation fault
和bus error
這類硬件錯誤並不在C++的範疇之內。
局部資源管理
什麼意思呢?就是new
申請的內存空間的釋放問題。首先看一段代碼:
extern Mutex m;
void f()
{
int *p = new int;
m.acquire();
process(p);
m.release();
delete p;
}
正常執行的情況下,p
和m
的內存最終都會釋放。但是如果process
中出現錯誤,那麼可能程序最終就沒有釋放內存。直觀的解決辦法就是在對應的catch
中寫入釋放內存的代碼段,但是有更好的解決辦法。即所謂的資源管理(resource managemet)手法,實際上即在初始化階段即進行資源請求(resource acquisition is initialization)。
聽起來名字很高端,其實主要是兩個技巧。其一是使用構造函數和析構函數初始化以及釋放所有變量。例如:
class MutexLock{
public:
MutexLock(Mutex m) : _lock(m)
{ lock.acquire();}
~MutexLock(){lock.acquire();}
private:
Mutex &_lock;
};
此時如果申請MutexLock
類型的對象,無論是否出現異常,析構函數一定會被執行,那麼內存一定可以釋放。
其二,是藉助函數庫#include <memory>
。具體使用方法是:
auto_ptr<int> p(new int);
其實相當於new
了int
類型的類實現,同時這個類還對操作符進行重載,使得基礎的操作和原始的變量相同,例如:
auto_ptr<string> aps(new string("vermeer"));
string *ps = new string("vermeer");
if (( aps->size() == ps->size()) && (*aps==*ps))
//...
這裏的*aps
和*ps
可以直接互相賦值。
標準異常
當我們收到一個異常時,如果我們希望對出現異常的變量進行操作。就需要藉助異常類體系(exception class hierarchy),這個類是基於一個抽象類exception
,同時抽象類聲明瞭一個what()
虛函數,會返回一個const char *
,用來表示拋出異常的文字描述。所以每個異常類體系中的派生類,都必須提供what()
的具體實現。例如:
#include <exception>
class iterator_overflow: public exception{
public:
iterator_oveflow(int index, int max)
: _index(index), _max(max)
{}
int index() { return _index;}
int max() { return _max; }
const char* what() const;
private:
int _index;
int _max;
};
同時,利用繼承類的規則catch
的輸入值只需要寫虛類的類型,就可以把這個庫中所有的錯誤類型包括進去,而不是每個寫一遍:
catch(const exception &ex)
{
cerr << ex.what() <<endl;
}
最後文中給出了what()
的一個實現:
#include <sstream>
#include <string>
const char*;
iterator_overflow::
what() const
{
ostringstream ex_msg;
static string msg;
ex_msg << ...
//從內存中導出數據並轉換爲string
msg = ex_msg.str();
//轉換爲const char*的表達式
return msg.c_str();
}
這裏需要強調的是ostringstream class
提供內存內的輸出操作,這需要調用sstream
頭文件。而.str()
可以將它轉換爲string
。另外c_str()
可以將string
轉換爲所需的const char*
。
完結撒花~ 還是收穫不少的~