17

To contextualize my question, I'm using a Matrix class with the following definitions:

Matrix(unsigned int, unsigned int); // matrix of the given dimension full of zeroes
Matrix(Matrix*); // creates a new matrix from another one
int &operator()(int, int);  // used to access the matrix
int **matrix; // the matrix

Now take these two code snippets:

First:

Matrix test(4,4);
Matrix ptr = test;
ptr(0,0) = 95;

Second:

Matrix test(4,4);
Matrix *ptr = &test;
(*ptr)(0,0) = 95;

Both codes have the same effect, the element in the (0,0) position receives 95 (The first snippet is very similar to Java, the reason which led me to ask this question). The question is, are both ways correctly making the assignment of the object?

user1493813
  • 981
  • 1
  • 8
  • 16

4 Answers4

37

This is a little complicated.

Consider this simple class:

class Thing1
{
public:
  int n;
}

Now we try the first experiment:

Thing1 A;
A.n = 5;

Thing1 B = A;
B.n = 7;

cout << A.n << " " << B.n << endl;

The result is "5 7". A and B are two separate, independent objects. Changing one doesn't change the other.

Second experiment:

Thing1 *p = &A;
p->n = 9;

cout << A.n << " " << p->n << endl;

The result is "9 9"; p is a pointer to A, so A.n and p->n are the same thing.

Now things get complicated:

class Thing2
{
public:
  int *p;
};

...
Thing2 A;
A.p = new int(2);

Thing2 B = A;
*(B.p) = 4;

cout << *(A.p) << " " << *(B.p) << endl;

Now the result is "4 4". The assignment B = A copied the pointer, so although A and B are two different objects, their pointers point to the same int. This is a shallow copy. In general, if you want to make a deep copy (that is, each Thing points to an int of its own) you must either do it by hand or give the class an assignment operator which will handle it. Since your Matrix class doesn't have an explicit assignment operator, the compiler gives it the default-- which is a shallow copy. That's why, in your first snippet, both matrices appear to be changed.

EDIT: Thanks to @AlisherKassymov, for pointing out that declarations of the form Thing A=B; use the copy constructor, not the assignment operator. So for the solution to work in the above code, the copy constructor must make a deep copy. (Note that if the copy constructor does it, you almost certainly want the assignment operator to do it too (see the Rule of Three)). Also note that if these functions get complicated, it makes sense to simply have the copy constructor invoke the assignment operator.)

Beta
  • 96,650
  • 16
  • 149
  • 150
  • Amazing answer! Thanks for the clear explanation of the concepts involved (shallow copy and deep copy). – user1493813 Nov 12 '13 at 02:28
  • 3
    I don’t think that this is correct. Making an assignment operator overload will not change the result in this case. This is a copy constructor. In order to make an assignment operator to work, you firstly need to construct the object and then use = to copy another object –  Jan 03 '18 at 17:45
  • @AlisherKassymov: **You're right, thank you.** I forgot that `Thing A=B;` uses the copy constructor, not the assignment operator (although the copy ctor is often implemented in terms of op=), and didn't test my solution far enough. I'll edit. – Beta Jan 19 '18 at 17:54
  • @Beta So suppose I typed `Thing2 B; B=A;` Do both objects' pointer point to the same memory address? –  Mar 16 '20 at 10:37
  • @user148469: Yes. If you want an assignment operator that makes a deep copy, you must write it yourself. – Beta Mar 17 '20 at 00:34
  • Uh...no? For @Terrarium's question, `B` and `A` are different objects. The compiler-provided assignment operator (and copy constructors) *will* copy the members. (They are sometimes called shallow because they don't dereference the pointers, but they work fine if we don't have dynamic data.) Do you agree? – flow2k Sep 03 '20 at 01:37
  • @flow2k: I do not agree. You have misread the text above. – Beta Sep 03 '20 at 03:37
  • @Beta Upon a rereading of Terrarium's question, I think it can also be interpreted to mean `*p` field. Is that what you understood when you initially answered the question? Frankly, I think the wording in the question is a bit ambiguous. But I don't think I said anything incorrect after possibly "Uh...no?". – flow2k Sep 09 '20 at 21:03
5

These two are not equal.

The first snippet

Matrix test, with is full content is COPIED into Matrix ptr. When you work with ptr later, you only change the copy, not the original Matrix test.

The second snippet

The address of Matrix test is put into the pointer Matrix *ptr. The pointer now holds the address of test. When you write (*ptr), you dereference the pointer and work with the value of the original test.

In Java

In java, all objects are kindof pointers (primitives like int are not). When you assign one object to another there, the default is to only overwrite the pointer value. Works like your second example.

Atle
  • 1,867
  • 12
  • 10
  • I'm finding it strange, using the first snippet both of them are being changed, i.e. the element in position (0,0) is set to 95. – user1493813 Nov 12 '13 at 02:03
  • Do you have a pointer inside the Matrix-class which points to the stored data? When you copy an object like this, any pointers inside the class are also copied and will point to the same values in the new class. – Atle Nov 12 '13 at 02:06
3

The first copies that value of test into ptr. The second sets ptr to be a pointer to the address of test

These two actions are not the same. In the first case, ptr will have the same value as test, but they are in them selves two distinct copies of that data, so that your assignment of ptr(0,0) = 95; will not set test(0, 0).

In the second instance however, ptr points to the address of test, so that the dereference of ptr is test. Thus, when you set the value here, you are actually setting the value of test(0, 0) as well.

This is trivially verifyable with a program such as this:

#include <iostream>
class Test{
public:
    int i;
};

int main(){
    Test c;
    c.i = 1;

    Test d = c;
    d.i = 2;
    std::cout << "Value: " << c.i << "\n";

    Test* e = &c;
    (*e).i = 2;
    std::cout << "Pointer: " << c.i << "\n";
}

Of course, if you dynamically allocate your Matrix (new) then when you copy the value as per your first example, the pointer to the data is also copied, so when you set the new data, it appears to be equal to the second example.

Sinkingpoint
  • 7,449
  • 2
  • 29
  • 45
  • My mistake was in assuming that values being copied were new values, but is the pointer to the data that is copied. Thanks for the clear answer the code example Quirliom, it was very helpful! – user1493813 Nov 12 '13 at 02:14
1

The equivalent of Java behavior in this case is expressed using C++ references

Matrix test(4,4);
Matrix &ptr = test;
ptr(0,0) = 95;

This code indeed does the same thing as the pointer version, i.e. it modifies the original test object.

Your first code sample formally creates a copy of the original object and then modifies that copy. However, your Matrix class appears to be a multi-level object that owns some lower-level memory (matrix pointer). If your Matrix class copy constructor implements shallow-copying logic (i.e. it shares the lower-level matrix data with the original object instead of deep-copying it), then modifying the copy will appear to modify the original object as well. Whether this behavior is correct or incorrect depends on your intent.

In your comments you mentioned that the first code also appears to modify the original object. This immediately means that your class in fact implements shallow-copying logic. And it looks like it is not an intended part of your design. Apparently you forgot to follow the Rule of Three when you were implementing your Matrix class.

Community
  • 1
  • 1
AnT stands with Russia
  • 312,472
  • 42
  • 525
  • 765