About C ++ Destructors

I have some Java experience and am new to C ++.

below is my code, its output:

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

I was expecting the following output:

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

I cannot understand why the destructor frees the first resource of the object. How can I print the result that 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(); } 
+7
source share
6 answers

If you increase the warning level on your compiler, you will get a hint that your class contains pointers, but you do not define Sequence(const Sequence&) or operator=(const Sequence&) (see What is Rule of Three? ).

Since you do not provide a copy constructor or assignment operator, the compiler provides them for you, which perform a membership assignment.

When you call s1 = Sequence(3,"s1"); , you do the following (this may be unexpected for a Java developer):

  • Create a new, temporary, Sequence of three with "s1" as its name
  • The purpose of this s1 is that:
    • sets si._content as a pointer to a new array of three ints just created, flowing from the old of 10.
    • sets si._count to 3
    • sets si._name to "s1"
  • Temporary (not s1 ) is then destroyed (in your actual output above you see that "s1" is destroyed twice), leaving _content pointing to free memory (so you see garbage on the second call to s1.show() ).

If you declare an assignment statement in this way, you get something closer to the expected result:

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

However, you will not see:

 destructor ---abc 

... because you are not destroying s1 , and its _name contains "abc" .

s1 destroyed when it goes out of scope at close } , so you see the second call to the destructor. With the code, you call delete[] on s1._content a second time (it was deleted under a temporary one, you will recall). This will likely cause a crash at the end of your program.

I added " (copy)" to _name in my assignment statement to help illustrate what is happening here.

Also pay attention to What is the idiom of copy and swap? , which is a very neat way of dealing with classes with raw pointers. This will also generate the desired result, since an instance of s1 with _name from "abc" gets swap and outputs and destroys. I implemented this one here , as well as several other small improvements so you can see how it works.

NB : the canonical way to instantiate a class:

 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 
+6
source

C ++ objects are very different from Java objects, and you come across a common point of confusion among those who are not familiar with C ++. Here's what happens:

 Sequence s1 = Sequence(); 

This creates a new Sequence s1 with a standard constructor (EDIT: at least what happens in the printout above, although, as several commentators have noted, it is absolutely necessary to create a temporary sequence, which is then assigned to s1 through the copy constructor).

 s1.show(); 

This displays the data on s1.

 s1 = Sequence(3,"s1"); 

Everything here becomes a little confusing. In this case, the following occurs:

  • A new anonymous Sequence object is built with parameters 3, "s1"
  • This anonymous object is copied (by value) to s1 using the = operator (copy operator)
  • Anonymous Sequence object falls out of scope and is deleted

Next last

 s1.show(); 

calls show () on the source object s1 again, but its data is now a copy of anonymous data.

Finally, s1 goes out of scope and is removed.

If you want objects that are more like Java objects, you need to treat them as pointers, for example.

 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 simplify memory management a bit, take a look at boost :: shared_ptr, which provides automatic memory management by counting (rather than garbage collection).

+3
source

As far as I can:

Sequence s1 = Sequence() : the default sequence (not the copy constructor), not a temporary, not called destructor.

s1.show() : prints values ​​to s1._content .

s1 = Sequence(3,"s1"); : creates a temporary object, uses an implicit copy constructor to assign s1 values. Deletes a temporary call to the calling destructor and, therefore, invalidates the pointer (_content) in s1 and temporary.

s1.show() : Undefined, since it prints with an invalid pointer.

Then, when s1 goes out of scope, it tries to remove s1._content ; more undefined.

+2
source

Line:

 Sequence s1 = Sequence(); 

Creates a temporary object and, using the constructor Sequence copy, copies it to s1 . Then it calls a temporary destructor. Since you do not have a copy constructor written, the bytes of the members of the anonymous object are copied to the new one, i.e. s1 . Then the temporary object goes out of scope and the destructor is called. The destructor prints the name and deletes the memory, which also owns s1 , so now s1 owns some deleted[] -format memory.

Then you do

 s1 = Sequence(3,"s1"); 

Why use the assignment operator for anonymous Sequence up to s1 . Here again, the anonymous object goes out of scope and the destructor is called, and s1 still owns the pointer to the destroyed memory.

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 this is that when creating a copy of Sequence new Sequence should not copy the pointer that was saved by the old Sequence (and point to the same memory block), but create a new memory block for yourself and copy all the data from the old Sequence memory block to the new .

There are probably several new concepts in this code, so study them and ask questions when you don’t understand something.

+1
source
 Sequence s1 = Sequence(); 

This creates two Sequence objects. The first is created by Sequence() . The second is created (by copy-building) on Sequence s1 . Or, in other words, this is equivalent to:

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

Sequence s1 does not create an object reference. He creates an object. Fully formed. You can do:

 Sequence s1; s1.show(); 

And that is wonderful.

If you want to call a constructor other than the standard, just do the following:

 Sequence s2(3,"s1"); 

To understand where this problem came from, look at this version:

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

You create a Sequence object. This forces the constructor to allocate the array using new . Good.

The second line takes a temporary Sequence object and copies it to s1 . This is called "copy destination."

Since you did not define a copy assignment operator, this means that C ++ will use the default copy algorithm. And this is just a byte copy (it also starts the assignment of a copy of class members). Therefore, instead of the Sequence calling its constructor, it gets the data copied into it from the temporary temp .

Here is the problem. Is source code temporary creation using Sequence() ? It is destroyed when this statement ends. It exists long enough for its contents to be copied to s1 , then it is destroyed.

Destruction means that its destructor is invoked. Its destructor will delete the array.

Now think about what happened. Temporary arose and allocated an array. The pointer to this array was copied to s1 . Then the temporary was destroyed, as a result of which the array was freed.

This means that s1 now contains a pointer to a freed array. This is why bare pointers are bad in C ++. Use std::vector instead.

Also, do not use such copy initialization. If you just want Sequence s1 , just create it:

 Sequence s1; 
+1
source

Let me explain what happens in your main function:

 Sequence s1 = Sequence(); 

After executing this separate line, several events occurred:

  • s1 is created using ctor by default.
  • Sequence() on the right also creates an unnamed time sequence object with the default ctor.
  • The temp object is copied to s1 using the operator = default function provided by the compiler. Thus, each field of the s1 member contains the same values ​​of the temporary object. Note that the _content pointer is also copied, so s1._content points to the data dynamically allocated for the _content pointer of the temporary object.
  • Then the temp object is destroyed because it goes out of scope. And this causes memory to be freed up on the _content pointer of the temporary object. However, since, as mentioned in 3, s1._content points to this memory block, this release calls s1._content, now it points to an already freed memory block, which means that you received garbage data in this memory block.

So, by this time, your output window should have: destructor --- abc

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

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

Similarly, s1 = Sequence(3,"s1"); also creates a temp object and copies all the data in s1. Now s1._name is "s1", s1._count is 3, and s1._content is the memory block allocated for the _content pointer of the temporary object.

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 

For the same reason, 2nd s1.show() also gives you garbage data, but with count = 3.

When all this is done, at the end of the main function, the object s1 is destroyed. And this will cause the problem that you are trying to delete a memory that is already freed (already deleted in the destructor of the 2nd tempo object).

The reason you saw the different output from mine may be your compiler, smart enough to prevent the construction of a temporary object using the default copy constructor.

Hope this helps.

+1
source

Source: https://habr.com/ru/post/894467/


All Articles