Essential C++ 學習筆記 第七章

異常處理

本來覺得這章會教怎麼解決常見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(),其執行結果是中斷程序。

至於trycatch以及throw如何安排,是非常值得商榷的。文中進行了大段的討論,這裏不做敘述。

另外需要區別的是,C++的每個異常都可以找到對應的throw。不過segmentation faultbus error這類硬件錯誤並不在C++的範疇之內。

局部資源管理

什麼意思呢?就是new申請的內存空間的釋放問題。首先看一段代碼:

extern Mutex m;
void f()
{
   int *p = new int;
   m.acquire();

   process(p);

   m.release();
   delete p;
}

正常執行的情況下,pm的內存最終都會釋放。但是如果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);

其實相當於newint類型的類實現,同時這個類還對操作符進行重載,使得基礎的操作和原始的變量相同,例如:

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*

完結撒花~ 還是收穫不少的~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章