2017-07-17 185 views
0

在下面的代碼中,如果某個數組元素的構造/銷燬會拋出什麼?new []/delete []並在C++中拋出構造函數/析構函數

X* x = new X[10]; // (1) 
delete[] x;  // (2) 

我知道內存泄漏被防止,但額外的:

  1. 廣告(1)中,先前構造的元件破壞?如果是的話,如果析構函數在這種情況下拋出會發生什麼?

  2. 廣告(2),是否還未被破壞的元素被破壞?如果是的話,如果析構函數再次拋出會發生什麼?

回答

5
  1. 是,如果x[5]構造拋出,然後x[0]..x[4]已經成功地構建了五個數組元素將被正確地破壞。

    • 破壞者不應該扔。如果一個析構函數確實拋出,那麼在前一個(構造函數)異常仍在處理的時候會發生這種情況。由於不支持嵌套異常,因此立即調用std::terminate。這是爲什麼析構函數不應該拋出。
  2. 這裏有兩個相互排斥的選項:

    1. 如果達到標籤(2),構造沒有拋出。也就是說,如果成功創建了x,則所有十個元素都已成功構建。在這種情況下,是的,它們全部被刪除。不,你的析構函數仍然不應該拋出。

    2. 如果構造函數拋出部分的方式,通過一步(1),則數組x根本不存在。該語言試圖爲你創建,失敗,並拋出一個例外 - 所以你根本沒有達到(2)

最關鍵的事情要明白的是,x要麼存在 - 在一個健全的和可預見的狀態 - 要麼沒有。

如果構造函數失敗,語言不會給你一些不可用的半初始化的東西,因爲無論如何你都無法做任何事情。 (你甚至不能安全地刪除它,因爲沒有辦法跟蹤哪些元素被構建,哪些只是隨機垃圾)。

這可能有助於將數組視爲具有10個數據成員的對象。如果你正在構造這樣一個類的實例,並且拋出了基類或成員構造函數之一,那麼所有先前構造的基類和成員將以完全相同的方式銷燬,並且對象永遠不會存在。

2

我們可以用下面的代碼進行測試:

#include <iostream> 

//`Basic` was borrowed from some general-purpose code I use for testing various issues 
//relating to object construction/assignment 
struct Basic { 
    Basic() { 
     std::cout << "Default-Constructor" << std::endl; 
     static int val = 0; 
     if(val++ == 5) throw std::runtime_error("Oops!"); 
    } 
    Basic(Basic const&) { std::cout << "Copy-Constructor" << std::endl; } 
    Basic(Basic &&) { std::cout << "Move-Constructor" << std::endl; } 
    Basic & operator=(Basic const&) { std::cout << "Copy-Assignment" << std::endl; return *this; } 
    Basic & operator=(Basic &&) { std::cout << "Move-Assignment" << std::endl; return *this; } 
    ~Basic() noexcept { std::cout << "Destructor" << std::endl; } 
}; 

int main() { 
    Basic * ptrs = new Basic[10]; 
    delete[] ptrs; 
    return 0; 
} 

此代碼會產生以下輸出崩潰之前:

Default-Constructor 
Default-Constructor 
Default-Constructor 
Default-Constructor 
Default-Constructor 
Default-Constructor 
[std::runtime_error thrown and uncaught here] 

注意,在任何時候被稱爲析構函數。這不一定非常重要,因爲未捕獲的異常無論如何都會導致程序崩潰。但是,如果我們捕獲錯誤,我們看到一些令人欣慰:

int main() { 
    try { 
     Basic * ptrs = new Basic[10]; 
     delete[] ptrs; 
    } catch (std::runtime_error const& e) {std::cerr << e.what() << std::endl;} 
    return 0; 
} 

輸出更改爲:

Default-Constructor 
Default-Constructor 
Default-Constructor 
Default-Constructor 
Default-Constructor 
Default-Constructor 
Destructor 
Destructor 
Destructor 
Destructor 
Destructor 
Oops! 

所以析構函數都會被自動調用的完全構造的對象,即使沒有明確的delete[]電話,因爲new[]調用有處理機制來處理這個問題。

但是你不必擔心第六個對象:在我們的例子中,因爲Basic沒有做任何資源管理(如果一個精心設計的程序不會有Basic做資源管理,如果它的構造函數可以像這樣拋出),我們不必擔心。但是,我們可能會擔心,如果我們的代碼看起來像這個:

#include <iostream> 

struct Basic { 
    Basic() { std::cout << "Default-Constructor" << std::endl; } 
    Basic(Basic const&) { std::cout << "Copy-Constructor" << std::endl; } 
    Basic(Basic &&) { std::cout << "Move-Constructor" << std::endl; } 
    Basic & operator=(Basic const&) { std::cout << "Copy-Assignment" << std::endl; return *this; } 
    Basic & operator=(Basic &&) { std::cout << "Move-Assignment" << std::endl; return *this; } 
    ~Basic() noexcept { std::cout << "Destructor" << std::endl; } 
}; 

class Wrapper { 
    Basic * ptr; 
public: 
    Wrapper() : ptr(new Basic) { 
     std::cout << "WRDefault-Constructor" << std::endl; 
     static int val = 0; 
     if(val++ == 5) throw std::runtime_error("Oops!"); 
    } 
    Wrapper(Wrapper const&) = delete; //Disabling Copy/Move for simplicity 
    ~Wrapper() noexcept { delete ptr; std::cout << "WRDestructor" << std::endl; } 
}; 

int main() { 
    try { 
     Wrapper * ptrs = new Wrapper[10]; 
     delete[] ptrs; 
    } catch (std::runtime_error const& e) {std::cout << e.what() << std::endl;} 
    return 0; 
} 

在這裏,我們得到如下的輸出:

Default-Constructor 
WRDefault-Constructor 
Default-Constructor 
WRDefault-Constructor 
Default-Constructor 
WRDefault-Constructor 
Default-Constructor 
WRDefault-Constructor 
Default-Constructor 
WRDefault-Constructor 
Default-Constructor 
WRDefault-Constructor 
Destructor 
WRDestructor 
Destructor 
WRDestructor 
Destructor 
WRDestructor 
Destructor 
WRDestructor 
Destructor 
WRDestructor 
Oops! 

Wrapper對象的大塊不會泄漏內存,但是第六Wrapper對象會因爲未正確清理而泄漏Basic對象!


幸運的是,通常任何的資源管理方案的情況下,所有這些問題消失,如果你使用智能指針:

#include <iostream> 
#include<memory> 

struct Basic { 
    Basic() { std::cout << "Default-Constructor" << std::endl; } 
    Basic(Basic const&) { std::cout << "Copy-Constructor" << std::endl; } 
    Basic(Basic &&) { std::cout << "Move-Constructor" << std::endl; } 
    Basic & operator=(Basic const&) { std::cout << "Copy-Assignment" << std::endl; return *this; } 
    Basic & operator=(Basic &&) { std::cout << "Move-Assignment" << std::endl; return *this; } 
    ~Basic() noexcept { std::cout << "Destructor" << std::endl; } 
}; 

class Wrapper { 
    std::unique_ptr<Basic> ptr; 
public: 
    Wrapper() : ptr(new Basic) { 
     std::cout << "WRDefault-Constructor" << std::endl; 
     static int val = 0; 
     if(val++ == 5) throw std::runtime_error("Oops!"); 
    } 
    //Wrapper(Wrapper const&) = delete; //Copy disabled by default, move enabled by default 
    ~Wrapper() noexcept { std::cout << "WRDestructor" << std::endl; } 
}; 

int main() { 
    try { 
     std::unique_ptr<Wrapper[]> ptrs{new Wrapper[10]}; //Or std::make_unique 
    } catch (std::runtime_error const& e) {std::cout << e.what() << std::endl;} 
    return 0; 
} 

和輸出:

Default-Constructor 
WRDefault-Constructor 
Default-Constructor 
WRDefault-Constructor 
Default-Constructor 
WRDefault-Constructor 
Default-Constructor 
WRDefault-Constructor 
Default-Constructor 
WRDefault-Constructor 
Default-Constructor 
WRDefault-Constructor 
Destructor 
WRDestructor 
Destructor 
WRDestructor 
Destructor 
WRDestructor 
Destructor 
WRDestructor 
Destructor 
WRDestructor 
Destructor 
Oops! 

請注意,撥打Destructor的電話數量現在與撥打Default-Constructor的電話數量相匹配,這表明Basic對象現在正在正確獲取清理乾淨。並且因爲Wrapper正在執行的資源管理已被委託給unique_ptr對象,所以第六個對象沒有其調用的刪除器的事實不再是問題。

現在,很多都涉及到草擬代碼:即使通過使用智能指針使其變得「安全」,沒有合理的程序員也不會有沒有適當處理代碼的資源管理器throw。但是有些程序員只是不合理,即使他們是,也可能會遇到一個奇怪的,異乎尋常的場景,你必須編寫這樣的代碼。就我而言,教訓是始終使用智能指針和其他STL對象來管理動態內存。不要試圖推出自己的。在嘗試調試時,它會像這樣爲您節省頭痛。

相關問題