Escalen
Escalen

Reputation: 317

Destructor on a Array of objects, that were already destructed

I am a bit new to Objects in C++, and I have the following simplified problem:

I want to create an array of objects that are already initialized by the constructer of the class.

Thus:

int main() {

 Test A(1);
 Test B(2);
 Test C(3);
 Test TestArray[3]={A,B,C};

 /*
  Code that both uses A,B,C directly and TestArray
 */

 return 0;
}

Importantly, the class Test dynamically allocates its value. And so the destructor should delete this allocated memory.

Thus:

class Test {
    int *PointerToDynMem;
public:
    Test(int);
    ~Test();
};

Test::Test(int a){
    PointerToDynMem=new int(a);
}

Test::~Test(){
    delete PointerToDynMem;
}

I think what happens is when the program ends A,B and C go out of scope and call the destructor. But it seems that also when TestArray goes out of scope, it also calls the destructor, but A,B,C were already deallocated soo. ERROR.

I always coded like this with normal type objects like an integer, and here it never gave me any problem. It seems that I need to change something, but don't know exactly how, since I want to both us the objects separately and have an array of them.

The thing I am confused about is why the Array should call that destructor, since it is basically a pointer to the first element and so not really an object going out of scope.

Upvotes: 1

Views: 1383

Answers (3)

NathanOliver
NathanOliver

Reputation: 180500

The thing I am confused about is why the Array should call that destructor, since it is basically a pointer to the first element and so not really an object going out of scope.

An array is not a pointer. If it was, we would just have pointers. An arrays name can and does decay to a pointer to the first element in the array (which can be really annoying) but it isn't a pointer. It is an object that stores N objects in a contiguous piece of memory and that size information is part of it's type. That means a int[5] is not the same type as a int[6].

What happens here is

Test TestArray[3]={A,B,C};

creates an array of three Test and then copy initializes each Test object in the array from the initializers {A,B,C}. So when main ends, TestArray is destroyed call the destructor for each element. And then C, B and A as destroyed in that order.

This is a problem for you though since you class just uses the default copy constructor. That means each object in the array has a copy of the point of the object it was initialized by and it points to the same memory. So when the array gets destroyed, delete is called on all of the member pointers and then C, B and A try to delete that same memory again, which is undefined behavior.

What you need to do is follow the rule rule of three and create the special member functions so your class is copied correctly, or you can use RAII in the form of smart pointers/std::vector and let them handle all of that for you and you can use the rule of zero

Upvotes: 2

bruno
bruno

Reputation: 32586

For that code you need to add a copy constructor (and also operator= etc for a 'real' code)

Test::Test(const Test & t) {
  PointerToDynMem=new int(*t.PointerToDynMem);
}

else your int* is shared (TestArray[0].PointerToDynMem is A.PointerToDynMem and TestArray[1].PointerToDynMem is B.PointerToDynMem and TestArray[2].PointerToDynMem is C.PointerToDynMem) and deleted 2 times

Upvotes: 1

Ted Lyngmo
Ted Lyngmo

Reputation: 117298

TestArray[3]={A,B,C}; will copy the int* held by A, B and C using the default copy constructor. To remedy this, create all constructors and operators that would otherwise be silently created for you. Read the rule of three/five/zero.

class Test {
    int *PointerToDynMem;
public:
    Test(int);
    Test(const Test&);            // implement this
    Test(Test&&);                 // implement this
    Test& operator=(const Test&); // implement this
    Test& operator=(Test&&);      // implement this
    ~Test();
};

Upvotes: 4

Related Questions