[英]About C++ destructors
我有一些Java經驗,並且是C ++的初學者。
下面是我的代碼,其輸出是:
0 1 2 3 4 5 6 7 8 9
destructor ---s1
8791616 8785704 2
destructor ---s1
我期待以下輸出:
0 1 2 3 4 5 6 7 8 9
destructor ---abc
0 1 2
destructor ---s1
我無法理解為什么析構函數釋放第一個對象的資源。 如何打印我預期的輸出?
#include <iostream>
using namespace std;
class Sequence{
public:
Sequence(int count=10,string name = "abc");
void show();
~Sequence();
int* _content;
int _count;
string _name;
};
Sequence::Sequence(int count,string name){
_count = count;
_content=new int[count];
_name = name;
for(int i=0;i<count;i++){
_content[i]=i;
}
}
Sequence::~Sequence(){
cout << "destructor ---"<<_name<<endl;
delete [] _content;
}
void Sequence::show(){
for(int i=0;i<_count;i++)
cout<<_content[i]<<" ";
cout<<endl;
}
int main(){
Sequence s1 = Sequence();
s1.show();
s1 = Sequence(3,"s1");
s1.show();
}
如果你提高編譯器的警告級別,你會得到一個提示,你的類包含指針,但你沒有定義Sequence(const Sequence&)
或operator=(const Sequence&)
(參見什么是三規則? )。
因為您沒有提供復制構造函數或賦值運算符,所以編譯器會為您提供這些,它們執行成員分配。
當你調用s1 = Sequence(3,"s1");
,您正在執行以下操作(這對Java開發人員來說可能是意外的):
Sequence
s1
,其中:
si._content
設置為指向剛剛創建的三個ints
的新數組的指針,泄漏舊的10個ints
。 si._count
設置為3
si._name
設置為"s1"
s1
)(在上面的實際輸出中,你看到“s1”被銷毀兩次),留下_content
指向free'd內存(這就是你在第二次調用s1.show()
看到垃圾的s1.show()
)。 如果您聲明這樣的賦值運算符,您將獲得更接近預期輸出的內容:
Sequence& operator =(const Sequence& rhs)
{
if (this != &rhs)
{
delete [] _content;
_count = rhs._count;
_content = new int[_count];
_name = rhs._name + " (copy)";
for (int i = 0; i < _count ; ++i)
{
_content[i] = rhs._content[i];
}
}
return *this;
}
但是,你不會看到:
destructor ---abc
...因為你的_name
包含"abc"
時不會銷毀s1
。
s1
當它超出范圍在閉幕式被破壞}
,這就是為什么你看到第二個析構函數調用。 使用您的代碼,這會s1._content
調用s1._content
的delete[]
(它會在臨時刪除,您會記得)。 這可能會導致程序結束時發生崩潰。
我在我的賦值運算符中為_name
添加了" (copy)"
來幫助說明這里發生了什么。
還請看一下什么是復制和交換習語? ,這是一個處理帶有原始指針的類的非常簡潔的方法。 這也會產生你想要的輸出,因為_name
為"abc"
的s1
實例會被swap
掉並被銷毀。 我已經在這里實現了這一點 ,還有一些其他的小改進,以便您可以看到它正常工作。
注意 :創建類實例的規范方法是:
Sequence s1; // Default constructor. Do not use parentheses [http://www.parashift.com/c++-faq-lite/ctors.html#faq-10.2]!
Sequence s2(3, "s2") // Constructor with parameters
C ++對象與Java對象有很大的不同,並且在C ++新手中遇到了一個常見的混淆點。 這是發生了什么:
Sequence s1 = Sequence();
這創建了一個新的Sequence,s1,帶有默認構造函數(編輯:至少這是上面打印輸出中發生的情況,盡管有幾位評論者指出,創建一個臨時序列然后通過分配給s1完全有效)而是復制構造函數)。
s1.show();
這將在s1上打印數據。
s1 = Sequence(3,"s1");
這是事情變得有點混亂的地方。 在這種情況下,會發生以下情況:
接下來,最后一個
s1.show();
再次調用原始s1對象上的show(),但其數據現在是匿名數據的副本。
最后,s1超出范圍,並被刪除。
如果您希望對象的行為更像Java對象,則需要將它們作為指針處理,例如
Sequence *s1 = new Sequence(); // constructor
s1->show(); // calling a method on a pointer
delete s1; // delete the old one, as it is about to be assigned over
s1 = new Sequence(3,"s1"); // assign the pointer to a new Sequence object
s1->show();
delete s1;
如果您想讓內存管理更容易一些,請查看boost :: shared_ptr,它提供引用計數(而非垃圾收集)自動內存管理。
盡可能簡單:
Sequence s1 = Sequence()
:默認構造的Sequence(不是復制構造函數),沒有臨時的,沒有析構函數被調用。
s1.show()
:在s1._content
打印值。
s1 = Sequence(3,"s1");
:創建臨時,使用隱式復制構造函數將值分配給s1。 刪除臨時,調用析構函數,從而使s1
和臨時中的指針(_content)無效。
s1.show()
:未定義的行為,因為它是從無效指針打印的。
然后當s1超出范圍時,它會嘗試刪除s1._content
; 更多未定義的行為。
這條線:
Sequence s1 = Sequence();
構造一個臨時對象,並使用Sequence
的復制構造函數將其復制到s1
。 然后它調用臨時的析構函數。 由於您沒有編寫復制構造函數,因此匿名對象成員的字節將復制到新的成員字節中,即s1
。 然后臨時對象超出范圍並調用析構函數。 析構函數打印名稱並刪除s1
也擁有的內存,所以現在s1
擁有一些deleted[]
ed內存。
然后你做
s1 = Sequence(3,"s1");
它使用賦值運算符將匿名Sequence
分配給s1
。 同樣在這里,匿名對象超出范圍並且析構函數被調用,並且s1
仍然擁有指向被破壞的內存的指針。
要解決此問題,您需要定義復制構造函數和賦值運算符:
Sequence::Sequence(const Sequence& rhs) : _name(rhs._name), _count(rhs._count), _content(new int[_count]) {
for (int i = 0; i < _count; ++i)
_content[i] = rhs._content[i];
}
Sequence& operator=(const Sequence& rhs) {
if (&rhs != this) {
delete[] _content;
_count = rhs._count;
_name = rhs._name;
_content = new int[_count];
for (int i = 0; i < _count; ++i)
_content[i] = rhs._content[i];
}
return *this;
}
原因是當你復制一個Sequence
,新的Sequence
不需要復制舊Sequence
所持有的指針(並指向同一塊內存),而是創建一個新的內存塊。本身並將舊Sequence
的內存塊中的所有數據復制到新內存塊中。
在該代碼中可能有幾個新的概念,所以研究一下,當你不理解某些東西時問問題。
Sequence s1 = Sequence();
這會創建兩個Sequence
對象。 第一個是由Sequence()
創建的。 第二個是由Sequence s1
創建的(通過復制構造)。 或者,換句話說,這相當於:
const Sequence &temp = Sequence();
Sequence s1 = temp;
Sequence s1
不會創建對象的引用。 它創建了一個對象 。 完全成型。 你可以做:
Sequence s1;
s1.show();
這完全沒問題。
如果要調用非默認構造函數,只需執行以下操作:
Sequence s2(3,"s1");
要了解問題的來源,請回顧此版本:
const Sequence &temp = Sequence();
Sequence s1 = temp;
您創建一個Sequence
對象。 這會導致構造函數使用new
分配數組。 精細。
第二行獲取臨時Sequence
對象並將其復制到s1
。 這稱為“復制分配”。
由於您沒有定義復制賦值運算符,這意味着C ++將使用默認的復制算法。 這只是一個字節副本(它還會觸發類成員的副本分配)。 因此,不是Sequence
調用其構造函數,而是從臨時temp
中將數據復制到其中。
這是問題所在。 在您的原始代碼中,您使用Sequence()
創建的臨時代碼? 該聲明結束時會被銷毀 。 它的存在時間足以將其內容復制到s1
,然后將其銷毀 。
銷毀意味着它的析構函數被調用。 它的析構函數將刪除該數組。
現在想想發生了什么。 臨時存在並分配了一個數組。 指向此數組的指針被復制到s1
。 然后臨時被破壞,導致數組被釋放。
這意味着s1
現在擁有一個指向解除分配數組的指針。 這就是為什么裸指針在C ++中很糟糕的原因。 請改用std::vector
。
另外,不要像這樣使用復制初始化。 如果您只想要一個Sequence s1
,請簡單地創建它:
Sequence s1;
讓我解釋一下主函數中會發生什么:
Sequence s1 = Sequence();
執行此單行后發生了幾件事:
Sequence()
還會創建一個具有默認ctor的未命名臨時序列對象。 所以到這個時候,你的輸出窗口應該有:析構函數--- abc
s1.show(); this shows the garbage data to the output window:
-572662307 -572662307 -572662307 -572662307 -572662307 -572662307 -572662307 -57 2662307 -572662307 -572662307
類似地, s1 = Sequence(3,"s1");
還會創建一個臨時對象並將所有數據復制到s1中。 現在s1._name是“s1”,s1._count是3,s1._content指向為臨時對象的_content指針分配的內存塊。
到這個時候,你將擁有:
destructor ---abc // first temp object
-572662307 -572662307 -572662307 -572662307 -572662307 -572662307 -572662307 -57
2662307 -572662307 -572662307 // first s1.show()
destructor ---s1 // second temp object
由於同樣的原因,第二個s1.show()
也給你垃圾數據,但count = 3。
當所有這些完成后,在main函數的末尾,s1對象被破壞。 這將導致您嘗試刪除已經解除分配的內存(已在第二個臨時對象的析構函數中刪除)的問題。
你之所以看到我的不同輸出的原因可能是你的編譯器“智能”足以消除使用默認復制構造函數構建臨時對象。
希望這可以幫助。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.