解構函式中拋異常

來源:互聯網
上載者:User

 1: 可以在解構函式中拋異常嗎?

        不可以!
        雖然文法上並沒錯,但會對整體系統帶來重大隱患!!

 2: 那麼如何保證析構不拋異常呢?
       2.1)析構裡如同建構函式一樣,做一些簡單的操作。
       2.2)如果異常不可避免,那麼直接在析構裡捕獲異常,不要讓異常逃離解構函式!

 3: 析構裡拋異常有什麼危害呢?
       阻止異常傳遞到解構函式外有兩個原因,第一能夠在異常轉遞的堆棧輾轉開解(stack-unwinding)的過程中,防止terminate被調用。第二它能協助確保解構函式總能完成我們希望它做的所有事情。
  
//////////////////////////////////////////////////////////////////////

  參考文章:C++中禁止異常資訊傳遞到解構函式外
  http://hi.baidu.com/ilotus_y/blog/item/21db051b22a54c1c8718bfa0.html

 在有兩種情況下會調用解構函式。第一種是在正常情況下刪除一個對象,例如對象超出了範圍或被顯式地delete。第二種是異常傳遞的堆棧輾轉開解(stack-unwinding)過程中,由異常處理系統刪除一個對象。

  在上述兩種情況下,調用解構函式時異常可能處於啟用狀態也可能沒有處於啟用狀態。遺憾的是沒有辦法在解構函式內部區分出這兩種情況。因此在寫解構函式時你必須保守地假設有異常被啟用,因為如果在一個異常被啟用的同時,解構函式也拋出異常,並導致程式控制權轉移到解構函式外,C++將調用terminate函數。這個函數的作用正如其名字所表示的:它終止你程式的運行,而且是立即終止,甚至連局部對象都沒有被釋放。

  下面舉一個例子,一個Session類用來跟蹤線上電腦的sessions,session就是運行在從你一登入電腦開始一直到登出出系統為止的這段期間的某種東西。每個Session對象關注的是它建立與釋放的日期與時間:

 

class Session ...{
public:
 Session();
 ~Session();
 ...
private:
 static void logCreation(Session *objAddr);
 static void logDestruction(Session *objAddr);
};

 函數logCreation 和 logDestruction被分別用於記錄對象的建立與釋放。我們因此可以這樣編寫Session的解構函式:

Session::~Session()
{
 logDestruction(this);
}

  一切看上去很好,但是如果logDestruction拋出一個異常,會發生什麼事呢?異常沒有被Session的解構函式捕獲住,所以它被傳遞到解構函式的調用者那裡。但是如果解構函式本身的調用就是源自於某些其它異常的拋出,那麼terminate函數將被自動調用,徹底終止你的程式。這不是你所希望發生的事情。程式沒有記錄下釋放對象的資訊,這是不幸的,甚至是一個大麻煩。那麼事態果真嚴重到了必須終止程式啟動並執行地步了嗎?如果沒有,你必須防止在logDestruction內拋出的異常傳遞到Session解構函式的外面。唯一的方法是用try和catch blocks。一種很自然的做法會這樣編寫函數:

 

Session::~Session()
...{
 try ...{
  logDestruction(this);
 }
 catch (...) ...{
  cerr << "Unable to log destruction of Session object "
   << "at address "
   << this
   << ". ";
 }
}

 

  但是這樣做並不比你原來的代碼安全。如果在catch中調用operator<<時導致一個異常被拋出,我們就又遇到了老問題,一個異常被轉遞到Session解構函式的外面。

  我們可以在catch中放入try,但是這總得有一個限度,否則會陷入迴圈。因此我們在釋放Session時必須忽略掉所有它拋出的異常:

Session::~Session()
{
 try {
  logDestruction(this);
 }
 catch (...) { }
}

  catch表面上好像沒有做任何事情,這是一個假象,實際上它阻止了任何從logDestruction拋出的異常被傳遞到session解構函式的外面。我們現在能高枕無憂了,無論session對象是不是在堆棧輾轉開解(stack unwinding)中被釋放,terminate函數都不會被調用。

  不允許異常傳遞到解構函式外面還有第二個原因。如果一個異常被解構函式拋出而沒有在函數內部捕獲住,那麼解構函式就不會完全運行(它會停在拋出異常的那個地方上)。如果解構函式不完全運行,它就無法完成希望它做的所有事情。例如,我們對session類做一個修改,在建立session時啟動一個資料庫事務(database transaction),終止session時結束這個事務:

Session::Session() // 為了簡單起見,,
...{ // 這個建構函式沒有
 // 處理異常
 logCreation(this);
 startTransaction(); // 啟動 database transaction
}

Session::~Session()
...{
 logDestruction(this);
 endTransaction(); // 結束database transaction
}

  如果在這裡logDestruction拋出一個異常,在session建構函式內啟動的transaction就沒有被終止。我們也許能夠通過重新調整session解構函式內的函數調用順序來消除問題,但是如果endTransaction也拋出一個異常,我們除了回到使用try和catch外,別無選擇。

  綜上所述,我們知道禁止異常傳遞到解構函式外有兩個原因,第一能夠在異常轉遞的堆棧輾轉開解(stack-unwinding)的過程中,防止terminate被調用。第二它能協助確保解構函式總能完成我們希望它做的所有事情。(如果你仍舊不很信服我所說的理由,可以去看Herb Sutter的文章Exception-Safe Generic Containers ,特別是“Destructors That Throw and Why They’re Evil”這段)。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.