天天看點

C++構造函數和析構函數中抛出異常

原文連結:http://blog.csdn.net/K346K346/article/details/50144947

從文法上來說,構造函數和析構函數都可以抛出異常。但從邏輯上和風險控制上,構造函數和析構函數中盡量不要抛出異常,萬不得已,一定要注意防止資源洩露。在析構函數中抛出異常還要注意棧展開帶來的程式崩潰。

1.構造函數中抛出異常

在C++構造函數中,既需要配置設定記憶體,又需要抛出異常時要特别注意防止記憶體洩露的情況發生。因為在構造函數中抛出異常,在概念上将被視為該對象沒有被成功構造,是以目前對象的析構函數就不會被調用。同時,由于構造函數本身也是一個函數,在函數體内抛出異常将導緻目前函數運作的結束,并釋放已經構造的成員對象,當然包括其基類的成員,即要執行直接基類和成員對象的析構函數。考察如下程式。

#include <iostream>
using namespace std;

class C{
    int m;
public:
    C(){cout<<"in C constructor"<<endl;}
    ~C(){cout<<"in C destructor"<<endl;}
};

class A{
public:
    A(){cout<<"in A constructor"<<endl;}
    ~A(){cout<<"in A destructor"<<endl;}
};

class B:public A{
public:
    C c;
    char* resource;

    B(){
        resource=new char[];
        cout<<"in B constructor"<<endl;
        throw -;
    }
    ~B(){
        cout<<"in B destructor"<<endl;
        delete[]  resource;
    }
};

int main(){
try{
        B b;
    }
    catch(int){
        cout<<"catched"<<endl;
    }
}
           

程式輸出結果:

in A constructor 
in C constructor 
in B constructor 
in C destructor 
in A destructor 
catched
           

從輸出結果可以看出,在構造函數中抛出異常,目前對象的析構函數不會被調用,如果在構造函數中配置設定了記憶體,那麼就會造成記憶體洩露,是以要格外注意。

此外,在構造函數B的對象b的時候,先要執行其直接基類A的構造函數,再執行其成員對象c的構造函數,然後再進入類B的構造函數。由于在類B的構造函數中抛出了異常,而此異常并未在構造函數中被捕捉,是以導緻類B的構造函數的執行中斷,對象b并未構造完成。在類B的構造函數“復原”的過程中,c的析構函數和類A的析構函數相繼被調用。最後,由于b并沒有被成功構造,是以main()函數結束時,并不會調用b的析構函數,也就很容易造成記憶體洩露。

2.析構函數中抛出異常

在析構函數中是可以抛出異常的,但是這樣做很危險,請盡量不要這要做。原因在《More Effective C++》中提到兩個:

(1)如果析構函數抛出異常,則異常點之後的程式不會執行,如果析構函數在異常點之後執行了某些必要的動作比如釋放某些資源,則這些動作不會執行,會造成諸如資源洩漏的問題。

(2)通常異常發生時,c++的異常處理機制在異常的傳播過程中會進行棧展開(stack-unwinding),因發生異常而逐漸退出複合語句和函數定義的過程,被稱為棧展開。在棧展開的過程中就會調用已經在棧構造好的對象的析構函數來釋放資源,此時若其他析構函數本身也抛出異常,則前一個異常尚未處理,又有新的異常,會造成程式崩潰。

那麼如果無法保證在析構函數中不發生異常, 該怎麼辦?

其實還是有很好辦法來解決的。那就是把異常完全封裝在析構函數内部,決不讓異常抛出析構函數之外。這是一種非常簡單,也非常有效的方法。

~ClassName()
{
 try{
      do_something();
  }
  catch(…){  //這裡可以什麼都不做,隻是保證catch塊的程式抛出的異常不會被扔出析構函數之外
   }
}
           

在面對析構函數中抛出異常時,程式猿要注意以下幾點:

(1)C++中析構函數的執行不應該抛出異常;

(2)假如析構函數中抛出了異常,那麼你的系統将變得非常危險,也許很長時間什麼錯誤也不會發生;但也許你的系統有時就會莫名奇妙地崩潰而退出了,而且什麼迹象也沒有,不利于系統的錯誤排查;

(3)當在某一個析構函數中會有一些可能(哪怕是一點點可能)發生異常時,那麼就必須要把這種可能發生的異常完全封裝在析構函數内部,決不能讓它抛出函數之外。

一定要切記上面這幾條總結,析構函數中抛出異常導緻程式不明原因的崩潰是許多系統的緻命内傷!

參考文獻

[1]Scott Meyers.More Effective C++[M].北京:電子工業出版社.2013[58-61]

[2]http://www.cnblogs.com/fly1988happy/archive/2012/04/11/2442765.html

繼續閱讀