簡體   English   中英

關於C ++析構函數

[英]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開發人員來說可能是意外的):

  • 使用“s1”作為名稱創建一個新的臨時三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._contentdelete[] (它會在臨時刪除,您會記得)。 這可能會導致程序結束時發生崩潰。

我在我的賦值運算符中為_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");

這是事情變得有點混亂的地方。 在這種情況下,會發生以下情況:

  1. 使用參數3“s1”構造新的匿名Sequence對象
  2. 使用operator =(復制運算符)將此匿名對象(按值)復制到s1
  3. 匿名Sequence對象超出范圍,並被刪除

接下來,最后一個

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();

執行此單行后發生了幾件事:

  1. 使用默認ctor創建s1。
  2. 右側的Sequence()還會創建一個具有默認ctor的未命名臨時序列對象。
  3. 使用編譯器提供的默認operator = function將temp對象復制到s1。 因此,s1的每個成員字段包含臨時對象的相同值。 請注意,_content指針也會被復制,因此s1._content指向為temp對象的_content指針動態分配的數據。
  4. 然后,臨時對象被破壞,因為它超出了它的范圍。 這導致臨時對象的_content指針上的內存釋放。 但是,正如3中所提到的,s1._content指向此內存塊,此釋放導致s1._content現在指向已釋放的內存塊,這意味着您在此內存塊中獲得了垃圾數據。

所以到這個時候,你的輸出窗口應該有:析構函數--- 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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM