简体   繁体   English

关于C ++析构函数

[英]About C++ destructors

I have some java experience and am a beginner on C++. 我有一些Java经验,并且是C ++的初学者。

below is my code, its output is: 下面是我的代码,其输出是:

0 1 2 3 4 5 6 7 8 9
destructor ---s1
8791616 8785704 2
destructor ---s1

I expected the following output: 我期待以下输出:

0 1 2 3 4 5 6 7 8 9
destructor ---abc
0 1 2
destructor ---s1

I can't understand why the destructor releases the first object's resource. 我无法理解为什么析构函数释放第一个对象的资源。 How can I print the output I expected? 如何打印我预期的输出?

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

If you increase the warning level on your compiler, you'll get a hint that your class contains pointers but you're not defining Sequence(const Sequence&) or operator=(const Sequence&) (see What is The Rule of Three? ). 如果你提高编译器的警告级别,你会得到一个提示,你的类包含指针,但你没有定义Sequence(const Sequence&)operator=(const Sequence&) (参见什么是三规则? )。

Because you don't provide the copy constructor or assignment operator, the compiler provides these for you, which perform member-wise assignment. 因为您没有提供复制构造函数或赋值运算符,所以编译器会为您提供这些,它们执行成员分配。

When you call s1 = Sequence(3,"s1"); 当你调用s1 = Sequence(3,"s1"); , you are doing the following (this may be unexpected to a Java developer): ,您正在执行以下操作(这对Java开发人员来说可能是意外的):

  • Creating a new, temporary, Sequence of three with "s1" as its name 使用“s1”作为名称创建一个新的临时三Sequence
  • Assigning this to s1 , which: 将此分配给s1 ,其中:
    • sets si._content to be the a pointer to the new array of three ints just created, leaking the old one of 10. si._content设置为指向刚刚创建的三个ints的新数组的指针,泄漏旧的10个ints
    • sets si._count to 3 si._count设置为3
    • sets si._name to "s1" si._name设置为"s1"
  • The temporary (and not s1 ) is then destroyed (in your actual output above, you see "s1" being destroyed twice), leaving _content pointing to free'd memory (which is why you see garbage on the second call to s1.show() ). 然后销毁临时 (而不是 s1 )(在上面的实际输出中,你看到“s1”被销毁两次),留下_content指向free'd内存(这就是你在第二次调用s1.show()看到垃圾的s1.show() )。

If you declare an assignment operator like this, you'll get something closer to your expected output: 如果您声明这样的赋值运算符,您将获得更接近预期输出的内容:

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;
}

You won't, however, see: 但是,你不会看到:

destructor ---abc

...because you don't destroy s1 while its _name contains "abc" . ...因为你的_name包含"abc"时不会销毁s1

s1 is destroyed when it goes out of scope at the closing } , which is why you see the second destructor call. s1当它超出范围在闭幕式被破坏} ,这就是为什么你看到第二个析构函数调用。 With your code, this calls delete[] on s1._content a second time (it was deleted under the temporary, you'll recall). 使用您的代码,这会s1._content调用s1._contentdelete[] (它会在临时删除,您会记得)。 This is likely to result in a crash right at the end of your program. 这可能会导致程序结束时发生崩溃。

I added " (copy)" to _name in my assignment operator to help to illustrate what is happening here. 我在我的赋值运算符中为_name添加了" (copy)"来帮助说明这里发生了什么。

Please also take a look at What is the copy-and-swap idiom? 还请看一下什么是复制和交换习语? , which is a very neat way to deal with classes with raw pointers. ,这是一个处理带有原始指针的类的非常简洁的方法。 This will also generate the output you desire as the instance of s1 with _name of "abc" gets swap ped out and destroyed. 这也会产生你想要的输出,因为_name"abc"s1实例会被swap掉并被销毁。 I've implemented this here , along with a few other little improvements so that you can see it working. 我已经在这里实现了这一点 ,还有一些其他的小改进,以便您可以看到它正常工作。

NB : The canonical way of creating an instance of a class is: 注意 :创建类实例的规范方法是:

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++ objects are rather different from Java objects, and you're running into a common point of confusion among those new to C++. C ++对象与Java对象有很大的不同,并且在C ++新手中遇到了一个常见的混淆点。 Here is what's happening: 这是发生了什么:

Sequence s1 = Sequence();

This creates a new Sequence, s1, with the default constructor (EDIT: at least that's what's happening in the printout above, although as several commenters have pointed out, it's perfectly valid for this to create a temporary Sequence which is then assigned to s1 via the copy constructor instead). 这创建了一个新的Sequence,s1,带有默认构造函数(编辑:至少这是上面打印输出中发生的情况,尽管有几位评论者指出,创建一个临时序列然后通过分配给s1完全有效)而是复制构造函数)。

s1.show();

This prints the data on s1. 这将在s1上打印数据。

s1 = Sequence(3,"s1");

This is where things get a bit confusing. 这是事情变得有点混乱的地方。 In this case, what happens is the following: 在这种情况下,会发生以下情况:

  1. A new anonymous Sequence object is constructed with the parameters 3,"s1" 使用参数3“s1”构造新的匿名Sequence对象
  2. This anonymous object is copied (by value) to s1, using operator= (the copy operator) 使用operator =(复制运算符)将此匿名对象(按值)复制到s1
  3. The anonymous Sequence object falls out of scope, and is deleted 匿名Sequence对象超出范围,并被删除

Next, the last 接下来,最后一个

s1.show();

calls show() on the original s1 object again, but its data is now a copy of the anonymous data. 再次调用原始s1对象上的show(),但其数据现在是匿名数据的副本。

Finally, s1 falls out of scope, and is deleted. 最后,s1超出范围,并被删除。

If you want objects that behave more like Java objects, you need to handle them as pointers, eg 如果您希望对象的行为更像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;

If you want to make the memory management a bit easier, look into boost::shared_ptr, which provides reference-counted (rather than garbage-collected) automatic memory management. 如果您想让内存管理更容易一些,请查看boost :: shared_ptr,它提供引用计数(而非垃圾收集)自动内存管理。

As simple as I can: 尽可能简单:

Sequence s1 = Sequence() : Default constructed Sequence (not copy constructor), no temporary, no destructor called. Sequence s1 = Sequence() :默认构造的Sequence(不是复制构造函数),没有临时的,没有析构函数被调用。

s1.show() : Prints the values in s1._content . s1.show() :在s1._content打印值。

s1 = Sequence(3,"s1"); : Creates a temporary, uses the implicit copy constructor to assign the values to s1. :创建临时,使用隐式复制构造函数将值分配给s1。 Deletes the temporary, calling the destructor, and hence invalidating the pointer (_content) in s1 and the temporary. 删除临时,调用析构函数,从而使s1和临时中的指针(_content)无效。

s1.show() : Undefined behavior, as it is printing from an invalid pointer. s1.show() :未定义的行为,因为它是从无效指针打印的。

Then as s1 goes out of scope, it attempts to delete s1._content ; 然后当s1超出范围时,它会尝试删除s1._content ; more undefined behavior. 更多未定义的行为。

The line: 这条线:

Sequence s1 = Sequence();

Constructs a temporary object, and, using Sequence 's copy constructor, copies it to s1 . 构造一个临时对象,并使用Sequence的复制构造函数将其复制到s1 It then calls the temporary's destructor. 然后它调用临时的析构函数。 Since you don't have a copy constructor written, the bytes of the members of the anonymous object are copied into a new one, which is s1 . 由于您没有编写复制构造函数,因此匿名对象成员的字节将复制到新的成员字节中,即s1 Then the temporary object goes out of scope and the destructor is called. 然后临时对象超出范围并调用析构函数。 The destructor prints the name and deletes the memory, which s1 also owns, so now s1 owns some deleted[] ed memory. 析构函数打印名称并删除s1也拥有的内存,所以现在s1拥有一些deleted[] ed内存。

Then you do 然后你做

s1 = Sequence(3,"s1");

Which uses the assignment operator to assign an anonymous Sequence to s1 . 它使用赋值运算符将匿名Sequence分配给s1 Again here, the anonymous object goes out of scope and the destructor is called, and s1 still owns a pointer to the destroyed memory. 同样在这里,匿名对象超出范围并且析构函数被调用,并且s1仍然拥有指向被破坏的内存的指针。

To fix this problem, you need to define a copy constructor and an assignment operator: 要解决此问题,您需要定义复制构造函数和赋值运算符:

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;
}

The reason for that is that when you make a copy of a Sequence , the new Sequence needs to not make a copy of the pointer that the old Sequence held (and point to the same block of memory) but create a new block of memory for itself and copy all the data from the old Sequence s block of memory to the new one. 原因是当你复制一个Sequence ,新的Sequence不需要复制旧Sequence所持有的指针(并指向同一块内存),而是创建一个新的内存块。本身并将旧Sequence的内存块中的所有数据复制到新内存块中。

There are probably several new concepts for you in that code, so study it a while and ask questions when you don't understand something. 在该代码中可能有几个新的概念,所以研究一下,当你不理解某些东西时问问题。

Sequence s1 = Sequence();

That creates two Sequence objects. 这会创建两个Sequence对象。 The first one is created by Sequence() . 第一个是由Sequence()创建的。 The second is created (by copy-construction) by Sequence s1 . 第二个是由Sequence s1创建的(通过复制构造)。 Or, to put it another way, this equivalent to: 或者,换句话说,这相当于:

const Sequence &temp = Sequence();
Sequence s1 = temp;

Sequence s1 doesn't create a reference to an object. Sequence s1不会创建对象的引用。 It creates an object . 它创建了一个对象 Fully formed. 完全成型。 You can do: 你可以做:

Sequence s1;
s1.show();

And that's perfectly fine. 这完全没问题。

If you want to call a non-default constructor, just do this: 如果要调用非默认构造函数,只需执行以下操作:

Sequence s2(3,"s1");

To understand where the problem comes from, look back at this version: 要了解问题的来源,请回顾此版本:

const Sequence &temp = Sequence();
Sequence s1 = temp;

You create a Sequence object. 您创建一个Sequence对象。 This causes the constructor to allocate an array with new . 这会导致构造函数使用new分配数组。 Fine. 精细。

The second line takes the temporary Sequence object and copies it into s1 . 第二行获取临时Sequence对象并将其复制s1 This is called "copy assignment". 这称为“复制分配”。

Since you did not define a copy assignment operator, this means that C++ will use the default copy algorithm. 由于您没有定义复制赋值运算符,这意味着C ++将使用默认的复制算法。 And that is simply a byte copy (it also triggers the copy assignment of the members of the class). 这只是一个字节副本(它还会触发类成员的副本分配)。 So instead of Sequence having its constructor called, it gets data copied into it from the temporary temp . 因此,不是Sequence调用其构造函数,而是从临时temp中将数据复制到其中。

Here's the problem. 这是问题所在。 In your original code, the temporary you create with Sequence() ? 在您的原始代码中,您使用Sequence()创建的临时代码? It is destroyed when that statement ends. 该声明结束时会被销毁 It exists long enough for its contents to be copied into s1 , then it is destroyed . 它的存在时间足以将其内容复制到s1 ,然后将其销毁

Destruction means that its destructor is called. 销毁意味着它的析构函数被调用。 Its destructor will delete the array. 它的析构函数将删除该数组。

Now think about what has happened. 现在想想发生了什么。 The temporary came into existence and allocated an array. 临时存在并分配了一个数组。 The pointer to this array was copied into s1 . 指向此数组指针被复制到s1 Then the temporary was destroyed, causing the array to be deallocated. 然后临时被破坏,导致数组被释放。

This means that s1 now holds a pointer to a deallocated array . 这意味着s1现在拥有一个指向解除分配数组的指针。 This is why naked pointers are bad in C++. 这就是为什么裸指针在C ++中很糟糕的原因。 Use a std::vector instead. 请改用std::vector

Also, do not use copy initialization like that. 另外,不要像这样使用复制初始化。 If you just want a Sequence s1 , create it simply: 如果您只想要一个Sequence s1 ,请简单地创建它:

Sequence s1;

Let me explain what happens in your main function: 让我解释一下主函数中会发生什么:

Sequence s1 = Sequence();

Several things happened after execution this single line: 执行此单行后发生了几件事:

  1. s1 is created with the default ctor. 使用默认ctor创建s1。
  2. Sequence() on the right also creates an unnamed temporary sequence object with default ctor. 右侧的Sequence()还会创建一个具有默认ctor的未命名临时序列对象。
  3. the temp object is copied to s1 with the default operator= function provided by compiler. 使用编译器提供的默认operator = function将temp对象复制到s1。 So each member field of s1 contains the same values of the temp object. 因此,s1的每个成员字段包含临时对象的相同值。 Note that the _content pointer also get copied, so s1._content points to the data dynamically allocated for the _content pointer of temp object. 请注意,_content指针也会被复制,因此s1._content指向为temp对象的_content指针动态分配的数据。
  4. Then the temp object is destructed because it's out of its scope. 然后,临时对象被破坏,因为它超出了它的范围。 And this causes the memory deallocation on _content pointer of temp object. 这导致临时对象的_content指针上的内存释放。 However, because as mentioned in 3, s1._content points to this memory block, this deallocation causes s1._content now points to a memory block already deallocated, which means you got garbage data in this memory block. 但是,正如3中所提到的,s1._content指向此内存块,此释放导致s1._content现在指向已释放的内存块,这意味着您在此内存块中获得了垃圾数据。

So by this time, your output window should have: destructor ---abc 所以到这个时候,你的输出窗口应该有:析构函数--- abc

s1.show(); this shows the garbage data to the output window:

-572662307 -572662307 -572662307 -572662307 -572662307 -572662307 -572662307 -57 2662307 -572662307 -572662307 -572662307 -572662307 -572662307 -572662307 -572662307 -572662307 -572662307 -57 2662307 -572662307 -572662307

Similarly, s1 = Sequence(3,"s1"); 类似地, s1 = Sequence(3,"s1"); also creates a temp object and copied all the data into s1. 还会创建一个临时对象并将所有数据复制到s1中。 Now s1._name is "s1", s1._count is 3 and s1._content points to the memory block allocated for the _content pointer of temp object. 现在s1._name是“s1”,s1._count是3,s1._content指向为临时对象的_content指针分配的内存块。

And by this time, you will have: 到这个时候,你将拥有:

destructor ---abc  // first temp object
-572662307 -572662307 -572662307 -572662307 -572662307 -572662307 -572662307 -57
2662307 -572662307 -572662307  // first s1.show()
destructor ---s1  // second temp object

Due to the same reason, 2nd s1.show() also gives you garbage data but with count = 3. 由于同样的原因,第二个s1.show()也给你垃圾数据,但count = 3。

When all these done, at the end of the main function, the s1 object gets destructed. 当所有这些完成后,在main函数的末尾,s1对象被破坏。 And this will cause the problem that you're trying to delete a memory which already get deallocated (already deleted in destructor of 2nd temp object). 这将导致您尝试删除已经解除分配的内存(已在第二个临时对象的析构函数中删除)的问题。

The reason why you saw different output from mine might be your compiler is "smart" enough to eliminate the construction of a temp object with a default copy constructor. 你之所以看到我的不同输出的原因可能是你的编译器“智能”足以消除使用默认复制构造函数构建临时对象。

Hope this helps. 希望这可以帮助。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM