[英]How can I understand these destructors?
我對以下C ++代碼感到困惑(可在http://cpp.sh/8bmp在線運行)。 它結合了我在課程中正在學習的幾個概念。
#include <iostream>
using namespace std;
class A {
public:
A() {cout << "A ctor" << endl;}
virtual ~A() {cout << "A dtor" << endl;}
};
class B: public A {
public:
B() {cout << "B ctor" << endl;}
~B() {cout << "B dtor" << endl;}
void foo(){cout << "foo" << endl;}
};
int main(){
B *b = new B[1];
b->~B();
b->foo();
delete b;
return 0;
}
輸出:
A ctor
B ctor
B dtor
A dtor
foo
A dtor
這是我不明白的:
foo
? delete
? delete b;
此代碼會泄漏內存嗎? A
的析構函數是虛擬的。 我認為不會調用子類中重載的虛函數。 為什么〜A ~A()
被調用? b->~B();
然后在foo
之后打印B dtor
行。 為什么? b->~B();
兩次,則輸出為: B dtor\\nA dtor\\nA dtor
。 ?? delete B;
我將得到相同的輸出delete B;
用delete[] b;
。 我認為第二個是正確的,因為b
是用new[]
創建的,但這沒關系,因為我只是將B
一個實例推入堆中。 那是對的嗎? 很抱歉問了這么多問題,但這令我感到困惑。 如果我的個人問題被誤導了,請告訴我了解每個析構函數何時運行所需的知識。
“未定義的行為”(簡稱UB)是允許編譯器執行任何操作的位置-這通常意味着介於“崩潰”,“給出錯誤的結果”和“仍然執行您期望的操作”之間。 您的b->foo()
絕對是未定義的,因為它發生在您的b->~B()
調用之后,
由於您的foo
函數實際上並沒有使用析構函數銷毀的任何東西,因此對foo
的調用“有效”,因為沒有使用任何已銷毀的東西。 [這是絕對不能保證的-它只是工作而已,有點像有時候跨過馬路不看就好,有時則不然。 視其路況而定,這可能不是一個好主意,或者在大多數情況下可能會奏效-但是有一個原因,人們說“向左看,向右看,向左看,然后在安全的情況下越過”(或類似的意思)那)]
在已被破壞的對象上調用delete
也是UB,所以再次幸運的是,它“有效”(就“不會導致程序崩潰”的意義而言)。
同樣,將delete
與new []
混合使用,反之亦然是UB-再次,編譯器[及其相關的運行時]可能根據情況和條件做對還是錯事。
不要依賴程序中未定義的行為[1]。 它一定會回來咬你。 C和C ++有很多UB案例,因此最好至少了解一些最常見的案例,例如“銷毀后使用”,“銷毀后使用”等,並留意此類情況-並避免不惜一切代價!
- 為什么在調用析構函數之后可以調用
foo
?
C ++並不會阻止您腳下射擊。 僅僅因為您可以做到(並且代碼不會立即崩潰),並不意味着它是合法的或定義明確的。
- 為什么在調用析構函數后可以調用
delete
?
與答案#1相同。
- 如果我注釋掉,
delete b;
此代碼會泄漏內存嗎?
是。 您必須 delete
您的new
(並delete[]
您的new[]
)。
A
的析構函數是虛擬的。 我認為不會調用子類中重載的虛函數。 為什么〜A()被調用?
我認為您想要的詞是優先 ,而不是過載 。 無論如何,您不會覆蓋〜A ~A()
。 注意~B()
和~A()
具有不同的名稱。
析構函數有點特殊。 當派生類的析構函數完成運行時,它將隱式調用基類的析構函數。 為什么? 因為C ++標准說的就是那樣。
虛擬析構函數是特殊的析構函數。 我讓您多態刪除一個對象。 這意味着您可以執行以下代碼:
B *b = new B;
A *a = b;
delete a; // Legal with virtual destructors, illegal without virtual.
如果A
在上面的代碼中沒有虛擬析構函數,則不會調用~B()
,這將是未定義的行為。 使用虛擬析構函數,編譯器將在delete a;
時正確調用~B()
delete a;
運行,即使a
是A*
而不是B*
。
- 如果我注釋掉
b->~B();
然后在foo
之后打印B dtor
行。 為什么?
因為它在foo()
之后運行。 delete b;
在foo()
已經運行之后,隱式調用b
的析構函數。
- 如果我重復行
b->~B();
兩次,則輸出為:B dtor\\nA dtor\\nA dtor
。 ??
這是未定義的行為。 所以任何事情都可能發生。 是的,那是奇怪的輸出。 未定義的行為很奇怪。
- 如果切換
delete B;
我將得到相同的輸出delete B;
用delete[] b;
。 我認為第二個是正確的,因為b
是用new[]
創建的,但這沒關系,因為我只是將B
一個實例推入堆中。 那是對的嗎?
您所謂的事情很重要。 delete
和delete[]
是不同的東西。 您不能用一個代替另一個。 您必須僅在分配有new
內存上調用delete
,並在分配有new
delete[]
的內存上調用delete
new[]
。 您不能隨意混合和匹配。 這樣做是未定義的行為。
您應該在此代碼中使用delete[]
,因為您使用了new[]
。
Q1:在被銷毀的對象上調用方法是“未定義的行為”。 這意味着該標准未指定應發生的情況。 UB背后的想法是,它們應該是應用程序邏輯中的錯誤,但是出於性能原因,我們不強迫編譯器對此做一些特殊的事情,因為如果正確執行操作,這會降低性能。
在這種情況下,由於foo()方法不依賴於b
指向的內存中的任何內容,因此它將按預期工作。 只是因為編譯器沒有對此進行任何測試。
Q2:這也是“未定義的行為”。 您已經看到一些奇怪的事情正在發生。 首先,不調用B析構函數,僅調用A析構函數。 發生的是,當您先前調用b->~B()
,調用了B析構函數,然后將對象的vtable更改為A vtable(這意味着對象運行時類型變為A),然后將A析構函數更改為叫。 當您調用delete b
,運行時將調用對象的虛擬析構函數A。如前所述,這是“未定義的行為”。 編譯器選擇生成在調用delete b
時以這種方式工作的代碼,但是它可能生成了不同的代碼,但仍然正確。
如果確實如此,則由於代碼中的另一個錯誤,在調用A析構函數后可能已經做得更糟:使用delete
而不是delete[]
。 C ++規則規定必須使用operator delete[]
釋放使用operator delete[]
operator new[]
分配的數組,而使用operator delete
則是“未定義的行為”。 實際上,我知道在大多數實現中,這樣做都是第一次,但是很有可能破壞內存管理數據,因此將來對new
或delete
甚至有效的調用都可能崩潰或導致內存泄漏。
Q3:如果您刪除該呼叫,將發生內存泄漏。 如果您繼續通話,則可能會導致內存損壞。 如果使用delete[] b
可以避免內存泄漏。 仍然存在未定義的行為,因為將對已銷毀的對象調用析構函數,但是由於這些析構函數不執行任何操作,因此它們可能不會對您的程序造成更多損害
問題4:這是所有析構函數的規則,而不僅僅是虛擬析構函數:它們將破壞成員對象,然后破壞代碼末尾的基礎對象。
Q5:因此,當編譯器為B析構函數生成代碼時,它將在最后添加對A析構函數的調用。 但是有一條規則:目前, this
不再是B對象,並且在對A析構函數中的任何虛擬方法的調用期間,即使是虛擬的,也必須調用A方法,而不是B方法。 因此,在調用A析構函數之前,B析構函數會將對象的動態類型從B“降級”到A(實際上,這意味着將對象的vtable設置為A vtable)。 由於編譯器的目標是生成有效的代碼,因此不必在A析構函數的末尾更改vtable。 請記住:在調用析構函數之后,對象上的任何方法調用都是“未定義的行為”。 優先是性能,而不是錯誤檢測。
問題6:與問題5的答案相同,我也談到了問題2
問題7:重要。 很多。 delete []需要知道創建的對象數量,以便可以為數組中的所有對象調用銷毀器。 通常,new []的實現實際上會分配一個size_t
元素,以將數組中對象的數量存儲在數組元素之前。 因此,返回的指針不是分配的塊的開始,而是大小之后的位置(在32位系統上為4個字節,在64位系統上為8個字節)。 因此,第一個new B[1]
將比new B
多分配4或8個字節,而第二個delete []將需要在分配它之前將指針遞減4或8個字節。 因此, delete b
和delete[] b
非常不同。
注意:編譯器沒有強制以這種方式實現new[]
和delete[]
。 並且某些實現提供了運行時庫的版本,該庫可以執行更多檢查,以便更容易檢測到錯誤。 但是,為了獲得最佳性能,如果使用new[]
則必須調用delete[]
。 而且,如果您在大多數情況下在對new[]
分配的指針上調用delete
的操作出錯,則第一次執行該操作不會崩潰或失敗。 稍后可能會在完全合法的new
, new[]
, delete
或delete[]
操作中崩潰或失敗。 這通常意味着很多人撓頭想知道為什么完全正確的操作會失敗。 這是“未定義行為”的真實含義
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.