-1

I wrote a matrix class that can do certain matrix operations, but my newest function, transpose(), triggers a breakpoint on delete[]. Here are the relevant parts:

class Matrix {
    float* M;
    int n, k;
    void allocM(int _n, int _k) {
        n = _n;
        k = _k;
        M = new float[n * k];
    }
    void delM() {
        delete[] M;
    }
    public:
    inline float operator()(int i, int j) const { return M[i * k + j]; }
    inline float& operator()(int i, int j) { return M[i * k + j]; }
    Matrix(int _n, int _k) {
        allocM(_n, _k);
        for (int i = 0; i < n; ++i) {
            for (int j = 0; j < k; ++j) {
                operator()(i, j) = 0;
            }
        }
    }
    Matrix(const Matrix& source) {
        allocM(source.n, source.k);
        for (int i = 0; i < n; ++i) {
            for (int j = 0; j < k; ++j) {
                operator()(i, j) = source(i, j);
            }
        }
    }
    ~Matrix() {
        delM();
    }
    Matrix& operator=(const Matrix& source) {
        delM();
        allocM(source.n, source.k);
        for (int i = 0; i < n; ++i) {
            for (int j = 0; j < k; ++j) {
                operator()(i, j) = source(i, j);
            }
        }
        return *this;
    }
    Matrix operator*(const Matrix& other) const {
        Matrix matrix = Matrix(n, other.k);
        for (int i = 0; i < matrix.n; ++i) {
            for (int j = 0; j < matrix.k; ++j) {
                for (int l = 0; l < k; ++l) {
                    matrix(i, j) += operator()(i, l) * other(l, j);
                }
            }
        }
        return matrix;
    }
    Matrix transpose() const {
        Matrix matrix = Matrix(k, n);
        for (int i = 0; i < n; ++i) {
            for (int j = 0; j < k; ++j) {
                matrix(k, n) = operator()(n, k);
            }
        }
        return matrix;
    }
};

I then multiply two matrices, basically. Something like:

matrix0 = matrix1 * matrix2.transpose();

Result: a breakpoint is triggered in delM() { delete[] M; }

I added a breakpoint to the matrix multiplication. This is what happens:

Program enters transpose() function, then Matrix(int, int) constructor runs. Then the nested loops run in transpose(), followed by return matrix;, where it runs Matrix(const Matrix& source), then back to return matrix;, then delM() and then it breaks on the line delete[] M;

I have only included the assignment operator function to show why I have a delM() function. I have tried zeroing *M out after declaration and in delM() after delete, but it didn't help.

What am I doing wrong?

PEC
  • 593
  • 1
  • 5
  • 16
  • 1
    please use smart pointer – Bryan Chen Oct 29 '14 at 21:18
  • is it not possible that a default-constructed matrix doesn't have its `M` initialized (i. e. it's an invalid, uninitialized pointer that cannot be `delete[]`d?) – The Paramagnetic Croissant Oct 29 '14 at 21:20
  • @TheParamagneticCroissant: `Matrix` has no default constructor, so no, it's not possible – Mooing Duck Oct 29 '14 at 21:21
  • @Bryan Chen This is a school assignment. I have a template source file that I can modify at certain places only. I am not allowed to include additional headers. – PEC Oct 29 '14 at 21:23
  • Code appears to work fine on my machine: http://coliru.stacked-crooked.com/a/f69fc3f38f38b12c – Mooing Duck Oct 29 '14 at 21:24
  • 2
    In the assignment operator, you're not checking that the source is the same as the destination. And in the innermost loop of transpose, you're using `(n, k)` instead of `(i, j)`. Otherwise this looks fine to me. Do you have more specific code that reproduces the crash? – Taylor Brandstetter Oct 29 '14 at 21:24
  • @TaylorBrandstetter There is a lot of code. Too much to paste it here, actually. I am programming a ray tracer. I need the transpose() function for the dot product operation on vectors. – PEC Oct 29 '14 at 21:34

3 Answers3

2

In transpose, you should change:

matrix(k, n) = operator()(n, k);

To:

matrix(j, i) = operator()(i, j);

You're accessing data off the end of the array M, causing heap corruption. It may be a good idea to do a bounds check in operator(), just to catch these kinds of things in the future.

And, as I mentioned in a comment, in operator=, add:

if (this == &source) return;

Or preferably (as Puppy points out), design it such that self-assignment is safe. Crude example:

    float *temp = M;
    allocM(source.n, source.k);
    for (int i = 0; i < n; ++i) {
        for (int j = 0; j < k; ++j) {
            operator()(i, j) = source(i, j);
        }
    }
    delete []temp;
    return *this;
Taylor Brandstetter
  • 3,523
  • 15
  • 24
  • 2
    NO SELF-ASSIGNMENT CHECK BAD BAD BOY VERY BAD – Puppy Oct 29 '14 at 21:32
  • @Puppy Because you could make an implementation that works with self-assignment but doesn't do an explicit check? True, but this requires the least code change. Enough to get a passing grade on the assignment... – Taylor Brandstetter Oct 29 '14 at 21:36
  • If by "could" you mean "is the only correct way", then yes. It requires the least code change and therefore PRESERVES ALL THE BUGS. – Puppy Oct 29 '14 at 21:36
  • Wow nice I wouldn't have noticed that in a year, thanks! – PEC Oct 29 '14 at 21:38
  • @Puppy If the Matrix class is used in a way where self-assignment is likely to occur, an explicit check could be a performance optimization. Though of course, such cases are unlikely. – Taylor Brandstetter Oct 29 '14 at 21:39
  • So tremendously unlikely, in fact, that it's well-known to be a general pessimization. Furthermore, requiring a self-assignment check for correctness directly implies that your assignment operator is fundamentally unsafe. – Puppy Oct 29 '14 at 21:40
  • @Puppy May be getting off-topic, but explain the "fundamentally unsafe" part? – Taylor Brandstetter Oct 29 '14 at 21:43
  • I think it's operator()(i, j) = temp[i * k + j], but I see what you are saying. Is creating and setting an additional float* actually faster than doing an explicit check for self-assignment, though? – PEC Oct 29 '14 at 22:08
  • @PEC - Please see my answer. All your concerns about the assignment operator become moot if you implement it using the method I outlined. No one has mentioned that your assignment operator is broken in another way, and that is that if new[] fails, you've messed up your object because you called `delM()` previously. There is no "rollback" in your design, so you're in trouble if something like that happens. The method I outlined removes that concern. – PaulMcKenzie Oct 29 '14 at 22:17
2

Your assignment operator is broken, along with every use of new and delete in your class. Use std::vector<float>, it has already solved all these problems for you in a far more efficient and correct way.

Puppy
  • 144,682
  • 38
  • 256
  • 465
  • Assignment operator is fixed now. I don't see how every new and delete is broken, but as I have commented under the post above, I am not allowed to include headers. – PEC Oct 29 '14 at 21:49
1

Your assignment operator, given that your copy constructor and destructor are working perfectly, can be written like this:

Matrix& operator=(Matrix source) 
{
   std::swap(source.n, n);
   std::swap(source.k, k);
   std::swap(source.M, M);
   return *this;
}

There is no need for a check for self-assignment, and it is exception safe. Your assignment operator you had written screws up the original object if new[] throws an exception. The code above alleviates it.

The code above uses the copy/swap idiom. If you follow the code, you will see how it works, and why it is an ingenious way of implementing an assignment operator given that the copy constructor and destructor are working.

The synopsis is that you create a temporary Matrix from the passed in argument, and then swap out what you created with the current (this) data. Then you let the temporary die off with the "old stuff", while the "new stuff" was given to this (that's what all the swapping is about).

The std::swap just swaps the two values with each other. If your requirements are so draconian that you can't call a std::swap function, then write your own and just call it, or write it "inline":

template <typename T>
void mySwap(T& x, T& y)
{
   T temp = x;
   x = y;
   y = temp;
}

Matrix& operator=(Matrix source) 
{
   mySwap(source.n, n);
   mySwap(source.k, k);
   mySwap(source.M, M);
   return *this;
}

Edit: Changed signature to allow compiler to make initial copy of the passed-in object.

PaulMcKenzie
  • 34,698
  • 4
  • 24
  • 45
  • Thanks man I put that into 2 of my classes just now. Seems so lazy though :D – PEC Oct 29 '14 at 23:21
  • Make sure that you swap all the members of the classes. If you leave any out, then the copy is broken. Also, it seems lazy, but this is idiom is described here: http://stackoverflow.com/questions/3279543/what-is-the-copy-and-swap-idiom – PaulMcKenzie Oct 30 '14 at 00:41