9

I am confused about how to use destructors when I have a std::vector of my class.

So if I create a simple class as follows:

class Test
{
private:
 int *big;

public:
 Test ()
 {
  big = new int[10000];
 }

    ~Test ()
 {
  delete [] big;
 }
};

Then in my main function I do the following:

Test tObj = Test();
vector<Test> tVec;
tVec.push_back(tObj);

I get a runtime crash in the destructor of Test when I go out of scope. Why is this and how can I safely free my memory?

Nigel
  • 135
  • 1
  • 2
  • 7
  • 1
    somehow my DevC++ "saved" me from these chashes. This code [http://www.ideone.com/EHZBV ] executes absolutely fine on DevC++ on Windows. Any ideas why that might be happening? This is not good. I want it to crash when it is supposed to crash! – Lazer May 01 '10 at 04:35

4 Answers4

21

The problem is you don't define a copy constructor for Test. So the compiler generates a default copy constructor for you, which just copies the content of the object - in this case the int pointer.

Now, when you push back your object into the vector, it is implicitly copied with the copy constructor. Which results in two objects pointing to the same array of ints! So in the end, two destructors try to delete the same array - BANG.

Whenever you define a class which owns members via pointers*, apart from the destructor you must also define a copy constructor for it. Update: and an assignment operator, for the same reason (thanks @James :-)

Update2: A trivial way to get around all these restrictions is to define a static array instead of the dynamically allocated one:

class Test
{
private:
  int big[10000];
  // no need for constructors, destructor or assignment operator
};

However, the best practice is to use std::vector<int> instead of an array.

* that is, contains pointers to members with ownership semantics (thanks to @Steve Jessop for clarification)

Péter Török
  • 114,404
  • 31
  • 268
  • 329
  • Exactly. When Test Object is added to the vector it is copied with the help of the copy constructor. You should crash if you try to access the array of the vector object also. – MatsT Apr 30 '10 at 14:56
  • "Whenever you define a class which contains pointer members" - with ownership semantics. A pointer without ownership semantics requires no special treatment from the class, but of course the caller has to ensure that the referand remains valid for as long as the object might use it. – Steve Jessop Apr 30 '10 at 15:19
  • @Steve, to me containment implied ownership, but you are right, I tried to make it clearer now. – Péter Török Apr 30 '10 at 19:04
16

Your problem is here:

Test tObj = Test();

The Test() creates a temporary Test object, which then gets copied to tObj. At this point, both tObj and the temporary object have big set to point to the array. Then the temporary object gets destroyed, which calls the destructor and destroys the array. So when tObj gets destroyed, it tries to destroy the already-destroyed array again.

Further, when tVec is destroyed, it will destroy its elements, so the already-destroyed array will be destroyed yet again.

You should define a copy constructor and an assignment operator so that when a Test object gets copied, the big array gets copied, or has some sort of reference count so that it doesn't get destroyed until all owners are destroyed.

An easy fix is to define your class like this:

class Test
{
private:
 std::vector<int> big;

public:
 Test (): big(10000) {}
};

In this case, you wouldn't need to define any destructor, copy constructor, or assignment operator, because the std::vector<> member will take care of everything. (But note that this means 10,000 integers get allocated and copied whenever you copy an instance of Test.)

Kristopher Johnson
  • 81,409
  • 55
  • 245
  • 302
  • Correct in principle but not in details. Test tObj = Test() won't create a temporary object. Copy constructor is called at tVec.push_back(tObj); – shura Apr 30 '10 at 15:38
  • 2
    @shura: Expression `Test()` in C++ creates a temporary object, by definition. Compilers are allowed to optimize it away, but in general case the temporary is created. – AnT stands with Russia Apr 30 '10 at 15:56
  • @AndreyT: I don't understand your 'by definition'. You say yourself that compilers are allowed to optimize it. And they do. Does the definition break at that point? Which compiler do you use that calls copy constructor for Test o = Test() ? – shura May 04 '10 at 12:27
  • @shura: Firstly, when I say "be definition", I'm talking specifically about the `Test()` subexpression. This subexpression creates a temporary object of type `Test`, by definition given in 5.2.3. Secondly, the whole thing `Test tObj = Test()` in C++ literally means "create a temporary and copy it with copy-constructor". Most compilers will decide to optimize it, since they are explicitly allowed to, but this is a completely different story. Moreover, *all* compilers will create a temporary if they decide not to optimize the code (due to compiler settings, or due to context etc.). – AnT stands with Russia May 04 '10 at 13:46
  • Whether I know a compier that actually performs the copyng does not matter. What matters is what the language specification says. Finally, *all* compilers (unless they are broken) will require the accessible copy-constructor to compile `Test tObj = Test()`, since conceptually the code performs *copying* of a temporary, even if the actual copying is optimized away. And I don't understand your point about "breaking the definition". Optimizations always *violate* the abstract C++ behavior, which is why they are called optimizations. In that sense, they do *break* various definitions. – AnT stands with Russia May 04 '10 at 13:50
  • 1
    not 10,000 bytes, but 10,000 ints – GuillemVS Oct 22 '22 at 20:45
0

Without a copy-constructor, the vector will create a flat copy of your object. This leads to two objects of type Test referencing the same array big. The first instance deletes the array when it gets destroyed, and then the second instance try to dereference a deleted pointer, which is undefined behavior.

Björn Pollex
  • 75,346
  • 28
  • 201
  • 283
0
Test tObj = Test();

This is wrong and should be as it does not create copies:

Test tObj;

This also create a lot of copies:

 vector<Test> tVec;
 tVec.push_back(tObj);

So if you free one int array, you'll free all the arrays. And the following delete will fail.

What you need is either:

  • use a copy constructor to have for each class a separate array

  • Why use a pointer?

class Test    
{   
private:  
  int big[10000];    
public:

};

This will work fine.

Nikko
  • 4,182
  • 1
  • 26
  • 44
  • Your suggestion will generally work fine, but there are platforms where putting an `int[10000]` on the stack would cause problems. Allocating large objects from the heap is generally safer. – Kristopher Johnson Apr 30 '10 at 15:08
  • It is hard to know exactly which platform the original poster uses, but maybe if he really knew why he has to use a heap allocation in this case maybe he would not have done his following mistakes. Well I don't know, maybe he should just use a std::vector after all? And of course, you can just create (smart) pointers of Test to avoid all those unnecessary copies – Nikko Apr 30 '10 at 15:48