構造函數失敗

什麼函數都有可能失敗,構造函數也不另外,比如new一個對象或空間不成功。當構造函數失敗的時候,其實很多時候我們不想這個對象被繼續生成,這個時候就可以在構造函數裏面拋出異常。C++規定構造函數拋出異常之後,對象將不被創建,析構函數也不會被執行,但已經創建成功的部分(比如一個類成員變量)會被部分逆序析構,不會產生內存泄漏。但有些資源需要在拋出異常前自己清理掉,比如打開成功的一個文件,最好關閉掉再拋出異常(雖然系統也會把這個資源回收),因爲拋出異常之後析構函數不會被執行了。

      網上比較經典的總結

       (1) C++中通知對象構造失敗的唯一方法那就是在構造函數中拋出異常;(這句話並不是說我們只有這個方法才能讓上層知道構造函數失敗,雖然構造函數沒有返回值,我們完全可以在構造函數中傳入一個引用值,然後在裏面設置狀態,運行完構造函數之後任然可以知道是否失敗,但這種情況下面對象其實還是被構造出來的,只是裏面有資源分配失敗而已,並且析構函數還是會執行。這和我們構造失敗不生成對象的初衷不符。)
  (2) 構造函數中拋出異常將導致對象的析構函數不被執行;(但已經生產的部分成員變量還是會被逆向析構的)
  (3) 當對象發生部分構造時,已經構造完畢的子對象將會逆序地被析構;


網上的一個例子

一個實例對象的構造
第一步,分配足夠的內存,如果失敗就是棧溢出或拋出std::bad_alloc的異常,所以在這步你不用擔心內存泄露,而且這一步你是不能插手的,如果這步成功,就進入第二步。

new運算符的實現保證了內存泄漏不會發生。例如

T *p = new T;

將被編譯器轉換給類似下面的樣子:(其實和我們自己釋放已經申請的資源的思想流程是一樣的)

// 第一步,分配原始內存,若失敗則拋出bad_alloc異常

try {

    // 第二步,調用構造函數構造對象

    new (p)T;       // placement new: 只調用T的構造函數

}

catch(...) {

    delete []p;     // 釋放第一步分配的內存

    throw;          // 重拋異常,通知應用程序

}


第二步,調用構造函數,在通常情況下,如果構造函數爲空或沒有進行動態內存分配,你就不用關心內存泄露了
你需要關心的是構造函數中有動態內存分配
class A
{
  char* str[10];
public:
  A(){
    for(int i=0;i<10;i++)
       str[i]=NULL;     //對str[]初始化,這是必須的,不然再後面delete就會出現問題
    try{
      for(int i=0;i<10;i++)
      str[i]=new char[1024*1024*1024];   //要來就來狠的
    }
    catch(bad_alloc){
       for(int i=0;i<10;i++)
           delete []str[i];  //放心,即使delete NULL是不會出問題的
       throw;  //就拋出這個bad_alloc, 這纔是構造函數拋出去的異常,外層會撲捉到,並且析構函數不會被調用
    }
  }
  ~A()
  {
    for(int i=0;i<10;i++)
     delete []str[i];         
  } 
};
int main()
{
    A *pA=NULL;
    try{
      pA=new A;
    }
    catch(bad_alloc){
       cout<<"Out of memory"<<endl;
    }
    delete pA;
    return 0;
}
pA是用NULL初始化的,即使在給A分配內存時(第一步)失敗,這樣就不會導致後面的delete pA出錯。



對於構造函數可能失敗的做法一般有兩種

1. 在構造函數中拋出異常,本對象構造未完成,它的析構函數不會被調用。當然,我們有義務釋放已經分配到的資源。簡單,最常見。
2. 把資源的初始化工作放在另一個單獨函數中,比如 bool init(...),由對象創建者(比如工廠方法)先調用構造函數,再調用init方法。ATL中常見。


本文轉自:http://blog.csdn.net/wind19/article/details/8213114

發佈了27 篇原創文章 · 獲贊 10 · 訪問量 9萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章