Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

About C++ destructors

Tags:

c++

destructor

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();
}
like image 222
zhangcheng Avatar asked Aug 07 '11 02:08

zhangcheng


2 Answers

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):

  • Creating a new, temporary, Sequence of three with "s1" as its name
  • Assigning this to s1, which:
    • sets si._content to be the a pointer to the new array of three ints just created, leaking the old one of 10.
    • sets si._count to 3
    • sets si._name to "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()).

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 swapped 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
like image 152
Johnsyweb Avatar answered Sep 21 '22 23:09

Johnsyweb


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:

  1. A new anonymous Sequence object is constructed with the parameters 3,"s1"
  2. This anonymous object is copied (by value) to s1, using operator= (the copy operator)
  3. The anonymous Sequence object falls out of scope, and is deleted

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.

like image 31
fluffy Avatar answered Sep 25 '22 23:09

fluffy