0

I'm trying to add two matrices using multidimensional arrays and overloading the addition operator '+' and the assignment operator '=' however, garbage data is passed to my overloaded assignment operator function.

I've been reading things about the Copy and Swap Idioms and so on but I can't seem to find a solution to my problem, and I'd prefer to use + and = separately, rather than '+=' which I've seen some people do, because I will use other arithmetic for matrices, but I want to get this solved first.

Here is my header:

#ifndef Matrix_h
#define Matrix_h
#include <stdio.h>
#include <stdlib.h>
#include <iostream>
#include <utility>

class Matrix
{
private:
    int row;
    int column;
    double  ** elements;

public:
    Matrix();
    Matrix(int r, int c); //constructor
    Matrix(const Matrix& src);  //copy constructor
    ~Matrix(); //destructor

    Matrix& operator=(const Matrix& right);  // assignment operator
    int getRow() const;
    int getColumn() const;
    void setElement(int r, int c, double data);
    double getElement(int r, int c) const;
    Matrix& operator+(const Matrix& right); // calculates the sum of two matrices

};

#endif 

Here are my function definitions:

#include <string.h>
#include "matrix.h"
using namespace std;

Matrix::Matrix(int r, int c){ //defines the constructor to create a new matrix
    row = r;
    column = c;
    elements = new double*[row];
    for (int i=0; i<row; i++){
        elements[i] = new double[column];
    }
    for (int i=0; i<row; i++){
        for (int j=0; j<column; j++){
            elements[i][j] = 0;
        }
    }
}

Matrix::Matrix(const Matrix& src){ //defines the copying constructor
    row = src.row;
    column = src.column;
    elements = new double*[row];
    for (int i=0; i<row; i++){
        elements[i] = new double[column];
    }
    for (int i=0; i<row; i++){
        for (int j=0; j<column; j++){
            elements[i][j] = src.elements[i][j];
        }
    }
}

Matrix::~Matrix() { //defines the destructor
    for (int i=0; i<row; i++){
        delete[] elements[i];
    }

    delete[] elements;
};

void Matrix::setElement(int r, int c, double data) {
    elements[r][c] = data;
};

double Matrix::getElement(int r, int c) const {
    return elements[r][c];
}

int Matrix::getColumn() const {
    return column;
}

int Matrix::getRow() const {
    return row;
}

Matrix& Matrix::operator =(const Matrix& right) {

    if(this->elements != right.elements && column==right.column && row==right.row)
    {
        memcpy ( &this->elements, &right.elements, sizeof(this->elements) );
     }

    return *this;
}

Matrix& Matrix::operator +(const Matrix& right) {
    // first, make sure matrices can be added. if not, return original matrix
    if (this->row != right.row || this->column != right.column){
        cout << "Matrix sizes do not match.";
        return (*this);
    }

    Matrix sum(row, column);
    for (int i=0; i<this->row; i++){
        for (int j=0; j<this->column; j++){
            sum.elements[i][j] = this->elements[i][j] + right.elements[i][j];

        }
    }
    return sum;
}

My main simply creates matrixA with a 4x4 matrix of 1s and matrixB with a 4x4 matrix of 2s and adds them where the sum = matrixC which should result in a 4x4 matrix of 3s. However, this is my result:

matrixA
1   1   1   1   
1   1   1   1   
1   1   1   1   
1   1   1   1   

matrixB
2   2   2   2   
2   2   2   2   
2   2   2   2   
2   2   2   2   

matrixC before addition
0   0   0   0   
0   0   0   0   
0   0   0   0   
0   0   0   0   

matrixC after addition
1.0147e-316 3   3   3   
1.0147e-316 3   3   3   
1.0147e-316 3   3   3   
1.0147e-316 3   3   3

When I test it to see what elements are passed to 'right' in the '=' function I noticed similar data as the first column of matrixC. I believe I'm not understanding the passing of references involved here fully so I'd like some help on that.

Kristin Vernon
  • 155
  • 1
  • 2
  • 20
  • 3
    Are you able to use http://eigen.tuxfamily.org ? It will save some headaches if you don't have to reinvent the wheel. – Tzalumen Mar 28 '19 at 16:12
  • At least one problem: in `operator =`, `memcpy` is not used correctly. It must be used for a set of contiguous elements, and you must indicate the exact number of bytes, which you don't obtain with `sizeof()` here. – Damien Mar 28 '19 at 16:14
  • Yea I have a version that's just straight `elements[i][j] = right.elements[i][j];` and that works so I can refer back to that. @Damien – Kristin Vernon Mar 28 '19 at 16:21
  • @Tzalumen I'll check it out! – Kristin Vernon Mar 28 '19 at 16:22

4 Answers4

2

Hmm, my CLang compiler warns me that operator +(const Matrix& right) return a reference to a temporary which is Undefined Behaviour.

You must return a plain object and not a reference:

Matrix operator=(const Matrix& right);  // assignment operator
Serge Ballesta
  • 143,923
  • 11
  • 122
  • 252
2
Matrix& Matrix::operator +(const Matrix& right) {
    // ...

    Matrix sum(row, column);
    // ...
    return sum;
}

You're returning a reference to an automatic local (sum).

You can see the canonical forms of the arithmetic operators here, and addition should look like

Matrix Matrix::operator+(const Matrix &b) const;

There are various other issues, some mentioned in other answers, but the only thing your current operator+ reliably produces is Undefined Behaviour.

Useless
  • 64,155
  • 6
  • 88
  • 132
  • Changing from `Matrix&` to `Matrix` gives me an error like: "double free or corruption (out)" – Kristin Vernon Mar 28 '19 at 16:30
  • 1
    Yeah, I said there were other problems too. This is the first one I spotted, and it's already sufficient that addition would never work. The assignment operator is also wrong, at least. – Useless Mar 28 '19 at 16:39
  • @KristinVernon That's because your `operator=` isn't implemented correctly either. – Max Langhof Mar 28 '19 at 20:55
2

Your assignment operator is broken (and logically flawed).

It's logically flawed because an assignment like m1 = m2 should result in m1 == m2, no matter what the previous state of m1 was. Your implementation only attempts to copy when they have the same size. You should resize the matrix on the left side of the assignment.

Your operator also isn't using memcpy correctly. memcpy(dest, src, count) will copy contiguous data from the address src to the address dest. The addresses you're providing are the addresses of pointers to pointers to int, not pointers to a contiguous block of ints. There are two levels of indirection here that memcpy simply has no clue about. Also, sizeof(this->elements) is sizeof(int**), which will be a constant of probably 4 or 8 bytes, which should also seem wrong. To fix this, I would just use a plain old set of for loops.

Matrix& Matrix::operator=(const Matrix& other){
    // attempt to acquire memory before modifying (for exception safety)
    int** temp = new int*[other.row];
    for (int i = 0; i < other.row; ++i){
        temp[i] = new int[other.col];
    }

    // cleanup
    for (int i = 0; i < row; ++i){
        delete[] elements[i];
    }
    delete[] elements;

    // copy data
    elements = temp;
    row = other.row;
    col = other.col;
    for (int i = 0; i < row; ++i){
        for (int j = 0; j < col; ++j){
            elements[i][j] = other.elements[i][j];
        }
    }

    return *this
}

Another important note: Your operator+ returns a reference to a local variable. This is very bad, but C++ doesn't protect you from it. You should just return sum by value.

In modern C++, we generally prefer to use safer alternatives to manual memory management like std::vector<std::vector<int>>, which go a long way to seamlessly enforce correct usage and to save you from yourself.

alter_igel
  • 6,899
  • 3
  • 21
  • 40
  • Thank you so much! This seemed to work, the use of `vector` I've read would be ideal but this is an assignment I'm helping out with so I didn't want to stray too far from what was assigned. So you created a new memory space and copy it into `this`? What's the purpose of cleaning `this` before? – Kristin Vernon Mar 28 '19 at 16:43
  • @KristinVernon the purpose of destroying the old data and allocating new data is to account for different sizes of matrices. For example, to copy a 10x10 matrix into a 3x3 matrix, you need to make that 3x3 matrix bigger to avoid reading memory incorrectly. The reason I allocate memory before modifying is for [exception safety](https://en.wikipedia.org/wiki/Exception_safety). It's admittedly unlikely that `new` will throw, but it might in some cases, and this ensures that the matrix is unchanged. – alter_igel Mar 28 '19 at 16:48
0

A hint: if you store the matrix as one array double m[rows * columns] the implementation is going to be much simpler.

Indexing [row][column] is m[row * columns + column].

Summing is just summing two arrays element-wise (vector sum essentially).

Maxim Egorushkin
  • 131,725
  • 17
  • 180
  • 271