I have some java experience and am a beginner on 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?).
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");
, you are doing the following (this may be unexpected to a Java developer):
Sequence
of three with "s1" as its names1
, which:
si._content
to be the a pointer to the new array of three ints
just created, leaking the old one of 10.si._count
to 3
si._name
to "s1"
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()
).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"
.
s1
is destroyed when it goes out of scope at the closing }
, which is why you see the second destructor call. With your code, this calls delete[]
on s1._content
a second time (it was deleted under the temporary, you'll recall). 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.
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. I've implemented this here, along with a few other little improvements so that you can see it working.
N.B: 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++. 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).
s1.show();
This prints the data on s1.
s1 = Sequence(3,"s1");
This is where things get a bit confusing. In this case, what happens is the following:
Next, the last
s1.show();
calls show() on the original s1 object again, but its data is now a copy of the anonymous data.
Finally, s1 falls out of scope, and is deleted.
If you want objects that behave more like Java objects, you need to handle them as pointers, e.g.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With