混沌 IN C++::Exception思維

難度:star.gifstar.gifstar.gifstar.gif

您的代碼中有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

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