so i just called it array and though it would be enough for you to understand what I mean. |
I understood that. But it doesn't change the fact that it's
actually a pointer and therefore the copy constructor will
not deep copy the data, and instead will merely shallow copy the pointer. That's what I was trying to draw attention to previously.
So to complete the class the copy and move-constructors should look like this to work properly, is that correct?
-code-
Do I miss something?
|
You almost have it. There are a few problems with that code --
and you are missing assignment operators and dtors (remember, if you need 1, then you need all 5). You'd need to allocate a buffer in the copy constructor.
And your move constructor isn't moving, but is destroying the buffer.
The class would look like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
|
class Data
{
int a, b, c;
int* array;
public:
Data() : a(), b(), c()
{
array = new int[200]; // this object allocates and owns this buffer
}
Data(const Data& d) :
a(d.a),
b(d.b),
c(d.c)
{
array = new int[200]; // this object allocates and owns this buffer
std::copy(d.array, d.array+200, array); // <- copy data for that buffer from 'd's buffer
}
Data(Data&& d) :
a(std::move(d.a)), // the std::moves here aren't really necessary because they are basic types.
b(std::move(d.b)), // but whatever.
c(std::move(d.c))
{
array = d.array; // we are not allocating an buffer, but are merely taking ownership of 'd's buffer
d.array = nullptr; // <- have 'd' release it so that it no longer owns it.
}
// destructor
~Data()
{
// delete whatever array we own
delete[] array;
}
// copy assignment
Data& operator = (const Data& d)
{
// no need to reallocate, because this object already owns a buffer. Simply copy
// the data over
a = d.a;
b = d.b;
c = d.c;
std::copy(d.array, d.array+200, array);
return *this;
}
// move assignment
Data& operator = (Data&& d)
{
// no need to reallocate -- or even to copy. We just want to move ownership of d's buffer
// to this object.
a = d.a;
b = d.b;
c = d.c;
delete[] array; // unallocate the buffer we currently own
array = d.array; // take ownership of d's buffer
d.array = nullptr; // have d release ownership of the buffer
return *this;
}
};
|
Again note:
- The compiler provided copy and move ctors/assignment operators will
not do this, but instead will do a shallow copy of the pointer, resulting in memory leaks and crashes due to the same buffer being deleted multiple times (or not at all).
- With the introduction of move semantics, the "rule of 3" has become "rule of 5". There are 5 notable functions:
1) Copy ctor
2) Move ctor
3) Copy assignment operator
4) Move assignment operator
5) Destructor
If you class needs to implement
any one of those 5, then it probably needs to implement
all of them.
- Notice how much code there is just to implement a simple dynamically sized array. This is 60 lines of code and the class doesn't even do anything. Also notice how error prone it is and how easy it is to make a mistake or forget to implement one of these. This is
exactly why manually managing memory is ill-advised, and why container classes like vector should be preferred.
By which I mean, this struct has pretty much identical functionaly to the above class:
1 2 3 4 5 6 7 8
|
struct Data
{
int a, b, c;
std::vector<int> array(200);
};
// Done. Because 'array' is now an object, it has functioning copy/move ctors, so the compiler-provided
// ctors for this struct will work exactly how you'd expect them to. We don't need any additional code.
|
By using a container instead of manually managing memory, we trimmed a 80 line error-prone class down to a 4-line impossible-to-screw-up struct.
but it comes down to the simple fact that the CopyConstructor is designed to copy data that is held by pointers |
The way this is worded is weird.
If you are talking about the desired effect of the copy ctor, then yes, that is correct.
But if you are talking about the compiler-provided copy ctor, then no -- this is incorrect.
I want to emphasize... the compiler-provided copy constructor does
NOT deep copy data that is held by pointers. It simply copies the pointer itself. It has no way to know what the pointer points to, and therefore it is
impossible for it to copy the data. If you need it to deep copy the data, you have to write the copy ctor yourself.