1
#include <iostream>
using namespace std;
class Matrix {

public:
  long int **Matr;
  long int n;

  void Create() {
    Matr = new long int *[n];
    for (int z = 0; z < n; z++)
      Matr[z] = new long int[n];
  }
  // constructors and destructor
  Matrix() : n(5) { Create(); }

  Matrix(long int i) : n(i) { Create(); }

  // Copy constructor
  Matrix(Matrix &N) {
    n = N.n;

    Matr = new long int *[n];

    for (int i = 0; i < n; i++)
      Matr[i] = new long int[n];

    for (int i = 0; i < n; i++) {
      for (int j = 0; j < n; j++) {
        Matr[i][j] = N.Matr[i][j];
      }
    }
  }

  Matrix operator*(Matrix &mx) {
    int i, j, k, num;
    Matrix result(n);
    for (int i = 0; i < n; i++)
      for (int j = 0; j < n; j++)
        num = 0;
    for (int k = 0; k < n; k++) {
      num += Matr[i][k] * mx.Matr[k][j];
    }
    result.Matr[i][j] = num;
    return result;
  }
  ~Matrix() {
    for (int z = 0; z < n; z++) {
      delete[] Matr[z];
    }
    delete[] Matr;
  }

  void Display() {
    for (int i = 0; i < n; i++) {
      for (int j = 0; j < n; j++) {
        cout << Matr[i][j];
      }
      cout << endl;
    }
  }

  Matrix operator+(Matrix &mx) {
    Matrix result(n);
    for (int i = 0; i < n; i++) {
      for (int j = 0; j < n; j++) {
        result.Matr[i][j] = Matr[i][j] + mx.Matr[i][j];
        // cout << result.Matr[i][j] << "\n";
      }
    }
    return result;
  }
};

int main() {
  Matrix M(2);
  M.Matr[0][0] = 0;
  M.Matr[0][1] = 1;
  M.Matr[1][0] = 2;
  M.Matr[1][1] = 3;

  Matrix N(2);
  N.Matr[0][0] = 0;
  N.Matr[0][1] = 1;
  N.Matr[1][0] = 2;
  N.Matr[1][1] = 3;

  Matrix C;

  C = M + N;
  cout << C.Matr[0][0];

  return 0;
}

I am doing a Matrix OOP class with some basic methods. I can't do "+" operator work in the way I want. In this case, when I'm trying to print C.Matr[0][0] it's printing random number for some reason, but if I simply remove destructor from code it works as expected and printing 0. Can someone help me to figure out why this is happening? Thanks a lot.

t.niese
  • 39,256
  • 9
  • 74
  • 101
  • 4
    Is there a reason why you're using `long int **Matr;` and `new long int *[n]` instead of `std::vector> Matr`? Using standard containers instead of `new` or `new []` resolves many problems. – t.niese Sep 21 '19 at 16:41
  • You should read [Why is "using namespace std;" considered bad practice?](https://stackoverflow.com/questions/1452721/why-is-using-namespace-std-considered-bad-practice). – Thomas Sablik Sep 21 '19 at 16:42
  • 3
    You need a copy assignment operator - check out the [rule of 3](https://en.m.wikipedia.org/wiki/Rule_of_three_(C%2B%2B_programming)) – Paul Sanders Sep 21 '19 at 16:45
  • This doesn't address the question, but the copy constructor should call `Create()` instead of duplicating that code. – Pete Becker Sep 21 '19 at 16:46
  • 2
    The `operator+` returns a temporary `Matrix`, which is assigned into `C` via the default `operator=`. At this point, `C` shares its `Matr` with the temporary, but the temporary's destructor destroys the shared `Matr`. – Justin Sep 21 '19 at 16:47
  • 1
    Please look up [rule of 3/5/0](https://en.cppreference.com/w/cpp/language/rule_of_three) – rustyx Sep 21 '19 at 17:00
  • 1
    Another issue is your `Create` function can fail to allocate memory in the middle of that loop, leaving you with memory leaks all over the place. You failed to take in consideration what happens if that occurs. I don't know why they still teach creating 2D arrays this way, given that this can happen -- it is the most flawed way of doing it. – PaulMcKenzie Sep 21 '19 at 17:21
  • Your `operator *` looks wrong to me. You nest two fors repeating `num=0;` many times -- why? Also, `result.Matr` is only written to outside the loops, hence only once, which looks wrong. – chi Sep 21 '19 at 19:02

3 Answers3

3

C is assigned a temporary with a shared Matr. The temporary dies immediately and you delete dangling pointers in the dtor.

The rule of three/five/zero will help you to prevent it.


What you are missing a copy assignment operator:

Matrix& operator=(const Matrix& N)
{
    // copy to temporary matrix
    long int tmpN = N.n;
    long int** tmpMatr = new long int*[tmpN];
    for (int i = 0; i < tmpN; i++)
        tmpMatr[i] = new long int[tmpN];

    for (int i = 0; i < tmpN; i++)
    {
        for (int j = 0; j < tmpN; j++)
             tmpMatr[i][j] = N.Matr[i][j];
    }

    // swap
    long int** swapMatr = Matr;
    long int swapN = n;
    Matr = tmpMatr;
    n = N.n;

    // deallocate old Matr
    for (int z = 0; z<swapN; z++){
        delete[] swapMatr[z];
    }  
    delete[] swapMatr;

return *this;
}

Online gdb

Oblivion
  • 7,176
  • 2
  • 14
  • 33
  • 1
    This will cause a memory leak. You failed to delete the old memory. – PaulMcKenzie Sep 21 '19 at 16:57
  • @PaulMcKenzie nice catch. fixed – Oblivion Sep 21 '19 at 17:00
  • 1
    This will now fail if `N.n > this->n`. The answer with the copy/swap is the simplest solution and avoids all of the issues I've mentioned so far. – PaulMcKenzie Sep 21 '19 at 17:38
  • @PaulMcKenzie Thanks. It should be fixed now. – Oblivion Sep 21 '19 at 17:50
  • Now you still have an issue -- your code is not exception safe. What if one of those calls to `new` throws an exception? You've corrupted your object, since you've changed the object's values before the assignment operator completed. Again, the copy / swap avoids this issue. If you really want to fix your code, you have to *allocate first*, copy the data to the allocated space, and **then** change the member variables. At the end, you deallocate the old space and assign the newly allocated space to your object. Read the comment I have in the main section concerning the `Create` function. – PaulMcKenzie Sep 22 '19 at 00:22
  • You also didn't check for self-assignment, yet another issue totally solved by copy / swap. To be honest, if a class has a working copy constructor and destructor, the best, and (I may be controversial but I don't care) the way an assignment operator is coded is using copy / swap. Easy to implement, always works, etc. – PaulMcKenzie Sep 22 '19 at 00:25
2

You must implement the assignment operator, not just the copy constructor and destructor.

The simplest way to implement the assignment operator is to use the copy / swap idiom:

  #include <algorithm>
  //...
  class Matrix
  {
     //...
     int **Matr;
     int n;
     //...
     public:
        Matrix& operator=(const Matrix& m)
        {
           if ( this != &m )
           {
              Matrix temp(m);  // create a copy of the passed in value
              std::swap(temp.Matr, Matr);  // swap out the internals with *this
              std::swap(temp.n, n);
           }  // Here temp's destructor removes the old memory that *this 
              // previously had
           return *this;
        }
        //...
     };

Note that this only works if you have a working, non-buggy copy constructor and destructor.

In addition, your copy constructor should be taking a const reference, not just a reference to Matrix:

Matrix(const Matrix& m) { ... }

Once you make those changes, the program no longer crashes, as seen here.

PaulMcKenzie
  • 34,698
  • 4
  • 24
  • 45
1

You don't have an assignment operator, so who knows what the line C = M + N; is doing?

Well, it's using the default assignment operator, which just copies each member in turn. In particular, the pointer Matr is copied from the pointer in the temporary M+N, so it points to memory which was allocated for the temporary, which is then destroyed.

Before you added the destructor, you had a memory leak, so the values persisted in the heap, and it appeared to work. After you added a destructor which freed the memory, it was apparently quickly overwritten.

Nick Matteo
  • 4,453
  • 1
  • 24
  • 35