1) All cap names, like MATRIX, are by convention used for constants. On the other hand, class names start with one cap: Matrix. Surprisingly enough, when you are used to that, an all caps class name like MATRIX is hard on the eyes.
2) Copy Constructor:
Any time you call a function and send it some arguments, copies of the arguments are made for the function. The same thing happens when you return something from a function: a copy is made, and the copy is returned. In your add() function, you do this:
Code:
MATRIX MATRIX::add(MATRIX m2)
{
MATRIX m(row,col);
...
...
return m;
}
When you return m, an m copy is made and returned. Then, when the function ends, m is destroyed.
The copy constructor comes into play because the copy constructor is what's used to make the m copy that is returned. Classes come with a lot of default stuff: you may already know about the default constructor and default destructor, but a class also comes with a default copy constructor. The default copy constructor that is supplied is said to make 'shallow' copies of the object. What that means is that it just copies everything exactly as it is in the original object. So, if you have a pointer in your original object, then the copy will have the exact same address and point to the same place in memory. In your add() function, the m object you created has a pointer in it:
Code:
class MATRIX
{
private:
int row,col;
int** x;
However, if you remember, I said before that when the add() function ends, m is destroyed:
Code:
MATRIX MATRIX::add(MATRIX m2)
{
MATRIX m(row,col);
...
...
return m;
}
That means the destructor you defined is called for m, and the memory that m's pointer points to is freed back to the system. But, the m copy that is returned is an exact copy of m, so the m copy has a copy of the m pointer. Therefore, the m copy will have a pointer that points to the memory that is no longer valid.
To solve that problem, you need to define a copy constructor that makes a 'deep' copy, i.e. instead of just copying m's pointer, your copy constructor needs to use m's pointer to get the actual data at that address, copy the data, and then assign it to a new pointer. That way, when m is destroyed, the m copy will have a pointer that points to a different location in memory, and therefore the memory will remain intact.
A copy constructor has a special form just like a regular constructor does: it doesn't return anything, and it takes a constant reference to the class as it's only parameter:
Matrix(const Matrix& rm)
It's imperative that the parameter be a reference type--otherwise a copy of the object would need to be made for the copy constructor function, which in turn would require a call to the copy constructor, etc., etc., and you end up getting an infinite chain of calls to the copy constructor.
2) Deleting:
Whenever you use new, you should always use delete to free up the memory when you are done with the memory. And, whenever you use 'new []', you need to use 'delete []'. What that means is: when you use "new []" to create an array, then when you delete, you use 'delete []' to delete the whole array. On the other hand, if you just used "new" to dynamically create the memory, then you just use "delete". In addition, the number of new's should equal the number of delete's: if you count the number of times you used new, there should be an equal number of deletes.
If you look at your example, you used 'new []' once to create the array of pointers, and you used 'new []' for each pointer in the array. So, to delete all the memory, you need to do this:
Code:
for(int j = 0; j < rows; j++)
{
delete [] x[j]; //deletes each one dimensional array
}
delete [] x; //deletes the array of pointers