2014-09-04 62 views
4
#include <cstdlib> 
struct B { 
    virtual void f(); 
    void mutate(); 
    virtual ~B(); 
}; 
struct D1 : B { void f(); }; 
struct D2 : B { void f(); }; 
void B::mutate() { 
    new (this) D2; // reuses storage — ends the lifetime of *this 
    f(); // undefined behavior - WHY???? 
    ... = this; // OK, this points to valid memory 
} 

我需要解釋爲什麼f() invokation有UB? new (this) D2;重用存儲,但它也調用D2的構造函數,並自啓動新對象的生命週期。 f()等於this -> f()這就是我們只需撥打D2的成員函數f()誰知道它爲什麼是UB?重用新對象的存儲開始生命週期嗎?

+0

Placement-new應該在大多數派生類中使用,並將它們替換爲相同類型的對象。這是您擁有UB的另一個原因,因爲您不僅要替換基類子對象,而且要用不同類型的對象替換它。 – 0x499602D2 2014-09-04 04:51:04

+0

@ 0x499602D2標準的18.6.1.3定義了這樣的位置的行爲 - 新的,但沒有說任何關於假設在大多數派生類中使用**以及創建相同類型的對象** – 2014-09-04 04:55:06

+4

3.8「如果在一個對象的生命週期已經結束,一個新的對象被創建在原始對象所佔據的存儲位置,[...]原始對象的名稱將自動引用新對象[...]用於在以下情況下操作新對象:原始對象是類型T的派生對象(1.8),新對象是類型T的派生對象(即,它們不是基類子對象)。 – 0x499602D2 2014-09-04 04:58:02

回答

1

該標準列出了該示例§3.8 67 N3690:

struct C { 
    int i; 
    void f(); 
    const C& operator=(const C&); 
}; 

const C& C::operator=(const C& other) { 
    if (this != &other) { 
    this->~C(); // lifetime of *this ends 
    new (this) C(other); // new object of type C created 
    f(); // well-defined 
    } 
    return *this; 
} 

C c1; 
C c2; 
c1 = c2; // well-defined 
c1.f(); // well-defined; c1 refers to a new object of type C 

注意,這個例子是就地建設新對象之前終止對象的生命週期(與你的代碼,這不叫析構函數)。

但即使你這樣做了,標準也說:

如果一個對象的生命週期結束之後和存儲 從而佔領被重用或釋放的對象,新的對象是 前在原始對象佔用的存儲位置創建一個 指向原始對象的指針,引用 爲原始對象,或者原始對象的名稱將爲 自動引用新對象,並且一旦 新對象的生命週期已經開始,可以用來操縱n EW對象,如果:

- 爲新對象存儲恰好覆蓋其原始對象佔據,和存儲位置 - 新的對象是 相同類型與原始對象的(忽略頂層 cv-qualifiers)和

- 原始對象的類型不是 const限定的,並且如果類類型不包含任何非靜態的 數據成員,其類型是const限定的或引用類型和

- 原始對象是一個最派生的對象(1.8)類型T和 新對象是類型T的最派生對象(即,它們不是 基類子對象)。

注意'和'字樣,上述條件必須全部滿足。

既然你沒有滿足所有的條件(你有一個派生類對象中,放入一個基類對象的內存空間),你有未定義行爲與隱性或顯性利用這個參考的東西時,指針。

取決於編譯器實現,這可能或可能現在吹因爲基類的虛擬對象保留用於虛表就地構建,其覆蓋了一個派生類型的對象的某些虛擬函數是指一些空間vtable可能與不同,將對齊問題和其他低級內部問題放在一起,你會發現簡單的sizeof不足以確定你的代碼是否正確。

+0

你是說他沒有終止舊對象的生命?標準說他是。 – 2014-09-06 15:58:22

+0

他並沒有破壞這個物體,這可能是非常糟糕的。爲了正確,我會指定這個,謝謝 – 2014-09-06 16:05:00

1

此結構是很有意思:

  • 放置新不保證調用對象的析構函數。所以這段代碼不會正確地確保對象的生命週期結束。

  • 所以原則上你應該在重用對象之前調用析構函數。但是,你會繼續執行一個死的對象的成員函數。根據標準部分。9.3.1/2 如果爲非X類型的對象或從X派生的類型調用類X的非靜態成員函數,則行爲未定義。

  • 如果不明確地刪除你的對象,當你在你的代碼做,你再重新創建一個新的對象(構建第二個B沒有destoying第一位的,然後D2這個新B的OT頂部)。

當你的新對象的創建完成後,在執行功能時,您當前對象的身份實際上已經改變了。你不能確定是否在放置之前讀取了將被調用的虛擬函數的指針 - 新的(因此是指向D1 :: f的舊指針)或之後(因此D2 :: f)。

順便說一下,正是出於這個原因,對於在union中可以或不可以做什麼有一些限制,其中爲不同的活動對象共享相同的存儲空間(參見第9.5/2點和特別是標準中的9.5/4)。

+1

Placement new不會清理舊對象,但會殺死它。一個對象的生命週期結束,如果它的內存被重用。 – 2014-09-06 15:57:18

+0

是的,真實的:一旦被覆蓋,舊的物體將不再顯示生命的跡象!然而,*「在對象的生命週期結束之後,在對象佔用的存儲器被重用之前」強烈建議原則上,生命的終結應該在重用之前發生,9.5/4點在另一個上下文中證明了這一點。這就是爲什麼我制定*「不會**正確**確保」*。例如,如果對象使用智能指針,則會直接覆蓋這些指針,並且它們的引用計數將會錯誤:使用對象可能會被殺死,但它仍然會困擾代碼! – Christophe 2014-09-06 16:35:14

+0

「*在對象的生命週期結束之後,在對象佔用的存儲被重用之前*」是析構函數運行時說的一種奇特的方式,如果您選擇運行*。在對象的生命週期結束之後,在對象佔用的存儲之前重新使用或釋放​​之前,在原始對象佔據的存儲位置創建新的對象*「是對內存事實的點頭不會消失。你可以用'free()'釋放它,但是你的libc仍然擁有它,並且會通過'malloc()'把它給回給你。 – 2014-09-06 16:36:13