難度:
您的代碼中有Exception嗎?作爲C++中最具爭議的東西,就連在使用上也不是一件容易的事,您怎麼看待異常呢?<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" />
如果想判斷一個對象是否構造成功,我們可以用以下的三種方法
1、
struct A
{
A(int& i)
{
//其他代碼
if(構造失敗)
i = 0;
else
i = 1;
}
};
int i;
A a(i);
if(i==0)
cout<<”構造失敗”<<endl;
2、
class A
{
public:
A()
{
//其他代碼
if(構造失敗)
isok_ = false;
else
isok_ = true;
}
bool isok() const
{ return isok_; }
private:
bool isok_;
};
A a;
if(!a.isok())
cout<<”構造失敗”<<endl;
3、
class my_exception: public std::exception{};
struct A
{
A()
{
//其他代碼
if(構造失敗)
throw my_exception();
}
};
try
{
A a;
}
catch(my_exception& ex)
{ cout<<”構造失敗”<<endl; }
綜觀三種方法,我們來做個簡單的分析。
第一種,該構造函數提供了一個用於返回錯誤的標誌變量,雖然可以判斷是否構造成功,但是這個多餘的變量更像是一個累贅。
第二種,構造函數上沒有歧義了。但是它並沒有降低複雜度。更重要的是,它和第一種方法有個通病,那就是析構函數會被正常調用。換句話說,這樣的對象構造失敗並不是語言所支持的,而是程序員自己的邏輯規定。
第三種,這就完全沒有上面兩種方法的問題,在構造函數中拋出異常,就意味這這個對象未構造成功,這是被語言所支持的,這樣一來,class A的析構函數將不會作用於對象a。這一特性可以讓我們不再爲這類安全性操心了。
在某些函數中,我們可以通過按值返回錯誤碼,但在某些情況下這類方法並不頂用,這樣我們就可以考慮異常。
從上面的表現來看,並沒有體現出異常帶來複雜度。但是,當遇到資源管理時,其中就有很多事情也許會被我們忽略。
void test()
{
int* p= new int;
//其他代碼
delete p;
}
如果中間的代碼拋出異常,最好的情況就是內存泄露及帶來不安全因素。我們應該加入異常處理
void test()
{
int *p =NULL;
try
{
p = new int;
//其他代碼
}
catch(...)
{
delete p;
throw; //異常中立,保證了前一版本test函數的行爲
}
delete p;
}
但這也許並不算最爽的解決方案。我們可以利用RAII技巧。來簡化這個操作
template<typename T>
class auto_new
{
public:
auto_new():ptr_(NULL)
{
try
{
ptr_ = new T;
}
catch(std::bad_alloc)
{
//異常處理
}
}
~auto_new()
{
delete ptr_;
}
operator T*()
{ return ptr_; }
private:
T* prt_;
}
void test()
{
auto_new<int> p;
//其他的代碼
}
這樣就不用擔心異常發生時帶來的資源回收問題。當然,對於簡單的資源,我們可以採用auto_ptr<>。
也許各位看官會認爲,這樣的做法也並沒有降低複雜度,似乎反而增大了工作量。是的,但是這樣的代碼可以使我們更放心。
異常也不是十全十美的,它自身也存在很多的缺陷,比如它的運行成本比較高,如果正常的控制結構可以處理錯誤,那麼就不應該去使用異常。異常的一個作用就是當某個部分出現異常狀況,那麼我們可以通過異常來通知另一個部分。例如,當程序出現異常,那麼我們可以把這個異常層層往上傳遞到函數的調用點,而其他的錯誤處理方式並不這麼方便。雖然異常可以在兩個部分進行傳遞,但是它並不是跨線程的,我們不能在兩個線程間傳遞異常。例如下面的代碼就是錯誤的。
DWORD CALLBACK threadfunc(void*)
{
//其他代碼
throw int(); //拋出異常
}
int main(){
try
{
DWORD tid;
HANDLE hdl = CreateThread(NULL, 0, &threadfunc, NULL, 0, &tid);
Sleep(500);
CloseHandle(hdl);
}
catch(...)
{ cout<<"catched"<<endl;}
}
threadfunc拋出的異常我們根本無法接收到。這樣也說明了一個問題,當我們不確定線程函數中的代碼是否會拋出異常的時候,我們都必須在其中加入try 塊,以保證異常安全。例如上面的代碼就應該寫成下面這個樣
DWORD CALLBACK threadfunc(void*)
{
try
{
//其他代碼 //不確定這裏是否會拋出異常
}
catch(...)
{}
}
int main(){
DWORD tid;
HANDLE hdl = CreateThread(NULL, 0, &threadfunc, NULL, 0, &tid);
Sleep(500);
CloseHandle(hdl);
}
//The End