views:

189

answers:

6

I'm making a very dumb mistake just wrapping a pointer to some new'ed memory in a simple class.

class Matrix
{
  public:
    Matrix(int w,int h) : width(w),height(h)
    {           
        data = new unsigned char[width*height];
    }

    ~Matrix() { delete data;    }

    Matrix& Matrix::operator=(const Matrix&p)
    {  
            width = p.width;
            height = p.height;
            data= p.data;
            return *this;
    }
    int width,height;
    unsigned char *data;
}

.........
// main code
std::vector<Matrix> some_data;

for (int i=0;i<N;i++) {
   some_data.push_back(Matrix(100,100)); // all Matrix.data pointers are the same
}

When I fill the vector with instances of the class, the internal data pointers all end up pointing to the same memory ?

+5  A: 

1. You're missing the copy constructor.

2. Your assignment operator should not just copy the pointer because that leaves multiple Matrix objects with the same data pointer, which means that pointer will be deleted multiple times. Instead, you should create a deep copy of the matrix. See this question about the copy-and-swap idiom in which @GMan gives a thorough explanation about how to write an efficient, exception-safe operator= function.

3. You need to use delete[] in your destructor, not delete.

John Kugelman
Note, however, that this assignment operator is not exception safe -- if your `data = new unsigned char[width * height];` fails, the old data is gone, but the new data hasn't replaced it, so your matrix is no longer valid.
Jerry Coffin
Thanks guys I wrote the assignment operator instead of the copy ctor! Been working with Qt doing this for you so long I forgot.
Martin Beckett
Yes, I don't even need an assignment operator - it got written by finger memory - just not very well! The delete [] was just a cut and paste simplyfing the code for here.
Martin Beckett
@Martin: Since you need a destructor, you __will need__ to think about assignment and copy-construction.
sbi
@Jerry and @sbi, thanks for the reminder. I've replaced my less-than-ideal implementation with a link to GMan's excellent answer.
John Kugelman
+3  A: 

You missed the copy constructor.

Matrix(const Matrix& other) : width(other.w),height(other.h)
{           
    data = new unsigned char[width*height];
    std::copy(other.data, other.data + width*height, data); 
}

Edit: And your destructor is wrong. You need to use delete[] instead of delete. Also you assignment operator is just copying the address of the already allocated array and isn't doing a deep copy.

pmr
And that assignment operator is wrong, too.
sbi
@sbi I figured others have added the information already and felt no need to duplicate it here. Changed.
pmr
+4  A: 

Whenever you write one of a copy-constructor, copy-assignment operator, or destructor, you should do all three. These are The Big Three, and the previous rule is The Rule of Three.

Right now, your copy-constructor doesn't do a deep copy. I also recommend you use the copy-and-swap idiom whenever you implement The Big Three.* As it stands, your operator= is incorrect.


Perhaps it's a learning exercise, but you should always give classes a single responsibly. Right now, yours has two: managing a memory resource, and being a Matrix. You should separate these so that you have one class that handles the resource, and another that uses said class to use the resource.

That utility class will need to implement The Big Three, but the user class will actually need not implement any of them, because the implicitly generated ones will be handled properly thanks to the utility class.

Of course, such a class already exists as std::vector.

GMan
* Yes, shameless self-plug.
GMan
I though it was the rule of 3.5
Martin York
@Martin: It was. But now that they invented rvalue references and we have move construction and move assignment, it's actually become the Rule of 4.25.
sbi
@Martin @sbi: Ha, I'll mention the copy-and-swap QA freely but I'm too shy to start throwing around my own terms. :) Indeed, I think Rule of 3.5 works well in C++03, and in C++0x it becomes the [Rule of 5](http://stackoverflow.com/questions/3280768/special-member-functions-in-c0x/3281119#3281119). (No 0.5 anymore, of course, as move-semantics imply swap-semantics.)
GMan
Can someone clue me in on what I'm missing about the 0.5? I don't follow the reference (or joke, if that's what it is).
Michael Burr
@Michael: It's within the copy-and-swap answer. Basically, whenever you implement The Big Three it probably also makes sense to add `swap` functionality as well, ergo we'll call it the Big Three (and A Half).
GMan
@Gman - yes, the Matrix() is just a standin for a more complex sparse matrix lib that I'm having problems with. This was just a 5min throw-away stub code that wasted an hour - especially because the test values were the same for each matrix so I didn't notice it was the same memory.
Martin Beckett
@Martin: Ah, gotcha. I would still try to separate sparse data storage from sparse data manipulation, so one class just needs to implement managing a resource while the other uses said resource.
GMan
+1  A: 

Your missing copy ctor has already been pointed out. When you fix that, you'll still have a major problem though: your assignment operator is doing a shallow copy, which will give undefined behavior (deleting the same data twice). You need either a deep copy (i.e., in your operator= allocate new space, copy existing contents to new space) or else use something like reference counting to ensure the data gets deleted only once, when the last reference to it is destroyed.

Edit: at the risk of editorializing, what you've posted is basically a poster-child for why you should use a standard container instead of writing your own. If you want a rectangular matrix, consider writing it as a wrapper around a vector.

Jerry Coffin
+1  A: 

You're using new[], but you aren't using delete[]. That's a really bad idea.

And your assignment operator makes two instances refer to the same allocated memory - both of which will try to deallocate it! Oh, and you're leaking the left side's old memory during assignment.

And, yes, you're missing a copy constructor, too. That's what the Rule of Three is about.

sbi
+1  A: 

The problem is you are creating a temporary with Matrix(100,100) that gets destructed after it is shallow copied into the vector. Then on the next iteration it is constructed again and the same memory is allocated for the next temporary object.

To fix this:

some_data.push_back(new Matrix(100,100));

You will also have to add some code to delete the objects in the matrix when you are done.

EDIT: Also fix the stuff mentioned in the other answers. That's important, too. But if you change your copy constructor and assignment operators to perform deep copies, then don't 'new' the objects when filling the vector or it will leak memory.

Amardeep
I never made the leap that this would cause the same memory to be alloced. I only need a shallow copy cos I'm only copying the pointer! I never thought of the dtor being called because I never debugged into the dtor - cos dtors can't fail can they ? I think I will use this as an interview question next time I'm hiring.
Martin Beckett
I can't do new into the vector because this was just a quick stub for a more complex Matrix class. But reference counted objects and smart poitners had made me lazy, I had forgotten to worry about this stuff.
Martin Beckett