C++程式設計思想(卷二):異常處理
www.firnow.com 時間 : 2009-11-09 作者:匿名 編輯:小張 點選: 105 [ 評論 ]
-
-
關鍵字throw将導緻一系列事情的發生:
首先,它将建立程式所抛出的對象的一個拷貝。
然後,包含throw表達式的函數傳回了這個對象,即使該函數原先并未設計為傳回這種對象類型。
另外,異常發生之前建立的局部對象被銷毀(調用對象的析構函數)。
一個異常被抛出以後,異常處理系統将按照在源代碼中出現的順序查找最近的異常處理器。一旦找到比對的異常處理器,就認為異常已經被處理了而不再繼續查找下去。
最好是通過引用而不是通過值來捕獲異常。
當存在派生類時,首先捕獲派生類異常,并且将基類放到最後用于捕獲其他不太具體的異常。
捕獲所有類型的異常:
用省略号代替異常處理器的參數清單就可以實作:
catch(...) { cout << "..." << endl; }
最好将它放在異常處理器清單的最後。省略号異常處理器不允許接受任何參數,這種catch子句經常用于清理資源并重新抛出所捕獲的異常。
在一個異常處理器内部,使用不帶參數的throw語句可以重新抛出異常,傳遞給位于更高一層語境中的異常處理器。
如果沒有任何一個層次的異常處理器能夠捕獲某種異常,一個特殊的庫函數terminate( )(在頭檔案<exception>中)會被自動調用。預設情況下,terminate( )調用标準C庫函數abort( )使程式執行異常終止而退出。
在構造函數中配置設定資源時,如果在構造函數中發生異常,析構函數将沒有機會釋放這些資源,這個問題經常伴随着“懸挂”指針出現,為了防止資源洩露,必須使用下列兩種方式之一來防止“不成熟的”資源配置設定方式:
1.在構造函數中捕獲異常,用于釋放資源。
2.在對象的構造函數中配置設定資源,并且在對象的析構函數中釋放資源。
為了實作第2種方式,可使用資源獲得式(Resource Acquisition Is Initialization,RAII)技術,因為它使得對象對資源控制的時間與對象的聲明周期相等。(可利用模闆來封裝指針,使得每個指針都被嵌入到對象中)。
由于在一個典型的C++程式中動态配置設定記憶體是頻繁使用的資源,是以C++标準中提供了一個RAII封裝類(auto_ptr類),用于封裝指向配置設定的堆記憶體的指針,這就使程式能自動釋放建立對象時占用的堆記憶體。
例:
auto_ptr類模闆是在頭檔案<memory>中定義的。
#include <memory>
#include <iostream>
#include <cstddef>
using namespace std;
class TraceHeap {
int i;
public:
static void* operator new(size_t siz) {
void* p = ::operator new(siz);
cout << "Allocating TraceHeap object on the heap "
<< "at address " << p << endl;
return p;
}
static void operator delete(void* p) {
cout << "Deleting TraceHeap object at address "
<< p << endl;
::operator delete(p);
}
TraceHeap(int i) : i(i) {}
int getVal() const { return i; }
};
int main() {
auto_ptr<TraceHeap> pMyObject(new TraceHeap(5));
cout << pMyObject->getVal() << endl; // Prints 5
}
exception類的定義在頭檔案<exception>中。exception類的兩個主要派生類為logic_error和runtime_error,這兩個類的定義在頭檔案<stdexcept>中(這個頭檔案包含<exception>)。logic_error和runtime_error都提供一個參數類型為std::string的構造函數,這樣就可以将資訊儲存在這兩種類型的異常對象中,通過exception::what( )函數,可以從對象中得到它儲存的資訊。
最好從runtime_error類或logic_error 來派生自己的異常類,而不要直接從std::exception類派生,因為它并沒有提供一個參數類型為std::string的構造函數。
void f( ); 意味着函數可能抛出任何類型的異常。
void f( ) throw( ); 意味着函數不會抛出任何異常。
異正常格說明:
預設的unexpected( )函數會調用terminate( )函數。
set_unexpected( )函數使用一個函數指針作為參數,這個指針所指向的函數沒有參數,而且其傳回值類型為void。
如果unexception處理器所抛出的異常還是不符合函數的異正常格說明,下列兩種情況之一将會發生:
1.如果函數的異正常格說明中包括std::bad_exception,unexpected處理器所抛出的異常會被替換成std::bad_exception對象,然後,程式恢複到這個函數被調用的位置重新開始異常比對。
2.如果函數的異正常格說明中不包括std::bad_exception,程式會調用terminate( )函數。
在派生類函數的異正常格說明中隻能指定範圍更小的異常或指定為不抛出異常。(在派生類函數中抛出異常時隻支援向下造型,不支援向上造型)
何時應該使用異常的最好建議是 :隻有當函數不符合它的規格說明時才抛出異常。
在以下情況不應該使用異常:
1.不要在異步事件中使用異常。(異常依賴于程式運作棧上的動态函數調用鍊)
2.不要在處理簡單錯誤的時候使用異常。
3.不要将異常用于程式的流程控制。
4.不要強迫自己使用異常。
5.新異常,老代碼。
在下列情況下請使用異常:
1.修正錯誤并且重新調試産生異常的函數。
2.在重新調試中的函數外面補償一些行為以便使程式得以繼續執行。
3.在目前語境中做盡可能多的事情,并把同樣類型的異常重新抛出到更高層的語境中。
4.在目前語境中做盡可能多的事情,并将一個不同類型的異常抛出到更高層的語境中。
5.終止程式。
6.将使用普通錯誤處理模式的函數(尤其是C庫函數)封裝起來,以便用異常來代替原有的錯誤處理模式。
7.簡化。如果建立的錯誤處理模式使事情變得更複雜并且難以使用,那麼異常可以使錯誤處理更加簡單有效得多。
8.使建立的庫和程式更安全。使用異常既是一種短期投資(為了調試友善),也是一種長期投資(為了應用系統的健壯性)。
不要在析構函數内部觸發異常:
如果在析構函數中抛出異常,這個新的異常可能會在現存的異常(其他異常)到達catch子句之前被抛出,這會導緻程式調用terminater( )函數。如果在析構函數中調用的函數可能會抛出異常,應該在這個析構函數中編寫一個try塊,并把這些函數調用放到try塊中,析構函數必須自己處理所有這些異常。絕對不能有任何一個異常從析構函數中抛出。
文章出處:飛諾網(www.firnow.com):http://dev.firnow.com/course/3_program/c++/cppsl/20091109/181786.html