[英]When is a `static_cast<Base*>(static_cast<void*>(derived))` from a pointer to a derived class valid?
[英]Safety of static_cast to pointer-to-derived class from base destructor
這是使用C ++中的Static_cast進行Downcasting的問題的變體,以及使用static_cast(或reinterpret_cast)進行無效向下轉換的安全性,無需添加成員
我不清楚標准“B實際上是D類對象的子對象的短語,結果指針指向類型D的封閉對象”關於~B中的行為。 如果你在~B中轉換為D,那么它仍然是一個子對象嗎? 以下簡單示例顯示了以下問題:
void f(B* b);
class B {
public:
B() {}
~B() { f(this); }
};
class D : public B { public: D() {} };
std::set<D*> ds;
void f(B* b) {
D* d = static_cast<D*>(b); // UB or subobject of type D?
ds.erase(d);
}
我知道演員是一個敞開大門的災難,從dtor做這樣的事情是一個壞主意,但同事聲稱“代碼是有效的,並且工作正常。這個演員是完全有效的。評論明確指出它不應該被解除引用“。
我指出演員陣容是不必要的,我們應該更喜歡類型系統提供的評論。 令人遺憾的是,他是高級/首席開發人員之一,也是一位理想的c ++“專家”。
我可以告訴他演員陣容是UB嗎?
[expr.static.cast] / P11:
類型為“指向cv1
B
指針”的prvalue,其中B是類類型,可以轉換為類型為“指向cv2D
指針”的prvalue,其中D
是從B
派生的類(第10條),如果是有效的標准從“指向D
指針”到“指向B
指針”的轉換存在(4.10), cv2與cv1相同,或者cv資格比cv1更高 ,並且B
既不是D
的虛基類也不是基類D
的虛擬基類。 空指針值(4.10)將轉換為目標類型的空指針值。 如果類型的prvalue“指針CV1B
”指向一個B
實際上是類型的對象的子對象D
,將所得指針指向類型的包圍對象D
。 否則,行為未定義。
的問題,然后,是,是否在時間static_cast
,指針實際指向“一B
實際上是類型的對象的子對象D
”。 如果是這樣,就沒有UB; 如果不是,則無論是否取消引用或以其他方式使用結果指針 ,行為都是未定義的。
[class.dtor] / p15說(強調我的)
一旦為對象調用析構函數,該對象就不再存在
和[basic.life] / p1說的那樣
類型
T
的對象的生命周期在以下情況下結束:
- 如果T是具有非平凡析構函數(12.4)的類類型,則析構函數調用將啟動,或者
- [...]
從那時起, D
對象的生命周期一旦被調用析構函數就結束了,當然也就是B
的析構函數開始執行時 - 這是在D
的析構函數體完成執行之后。 此時,沒有“類型D
對象”,這個B
可以是 - 它“不再存在”的子對象。 因此,你有UB。
顯然你的同事的印象是,只要你沒有取消引用無效指針,你就沒事了。
他錯了 。
僅僅評估這樣的指針具有未定義的行為。 這段代碼顯然已被破壞。
你應該明確告訴他這是UB! !
為什么?
12.4 / 7:基礎和成員按照構造函數完成的相反順序銷毀。對象按其構造的相反順序銷毀。
12.6.2 / 10:初始化第一個(...)虛擬基類(...)然后,初始化直接基類
因此,當破壞D時,首先破壞D成員然后破壞D子對象,然后才破壞B。
此代碼確保在銷毀B對象時調用f()
:
~B() { f(this); }
因此,當D對象被銷毀時,首先銷毀D子對象,然后執行~B(),調用f()
。
在f()
您將指針轉換為B作為指向D的指針。 這是UB:
3.8 / 5: (...)在對象的生命周期結束之后,在重用或釋放對象占用的存儲之前,可以使用任何指向對象所在或所在的存儲位置的指針, 但只是在有限的方式 。 (...)如果指針用於訪問非靜態數據成員或調用對象的非靜態成員函數,或者(...) 指針用作操作數,則程序具有未定義的行為 。 static_cast 。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.