简体   繁体   中英

Difference between reference assignment in C++ and Java

I have been learning C++ for two weeks. In Java, if we have two different objects of the same class, and if we assign the reference of one object to the other reference of the other object, then they refer to the same object. Afterward, changing a data member by one reference would change the data member in the other reference as well. My question is: Isn't it the same in C++ too? I got a bit confused about Copy constructors and assignment operators. Both of them do deep copy. Without them, we can only do shallow copy as far as I know. I have a code snippet too.

#include <iostream>
using namespace std;

class Test
{
    int x;
    int &ref;

    public:
        Test(int i):x(i), ref(x) {}
        void print() { cout << ref;}
        void setX(int i) {x = i;}
        Test &operator = (const Test &t) {x = t.x; return *this;}
};

int main()
{
    Test t1(10);
    Test t2(20);
    t2 = t1;
    t1.setX(40);
    t2.print();  // This will print 10
    cout << "\n\n";
    t1.print();  // This will print 40
    return 0;
}

In Java, if we have two different objects of the same class, and if we assign the reference of one object to the other reference of the other object, then they refer to the same object.

My question is: Isn't it the same in C++ too?

It isn't the same at all. In C++, references cannot be reassigned to refer to another object. They refer to the same object throughout their entire lifetime. When assignment operation is applied to a reference, then the referred object is assigned.

Note that Java has no explicit references. All class type variables are references, and primitive variables are value-objects. C++ is different. You have to explicitly specify whether a variable is a reference or an object, and you can have value-objects of class types as well as references to fundamental types.

In some ways Java references are more similar to C++ pointers than C++ references. In particular, pointers can be null, and can be assigned to point elsewhere, just like Java references.

// Java
int i = 0;             // not a reference
i = some_int;          // i is modified
Test tref = null;      // a reference
tref = t;              // reference is modified
tref = other_t;        // reference is modified; t is not modified

// C++
Test t;                // not a reference
Test& tref = t;        // a reference
t = other_t;           // t is modified
tref = other_t;        // t is modified; reference is not

Test* tptr = nullptr;  // a pointer (not a reference)
tptr = &t;             // tptr is modified
*tptr = other_t;       // t is modified
tptr = other_t;        // tptr is modified; t is not modified



 

Looking at your statement:

I have been learning C++ for two weeks.

Congratulations a good step forward. :-)

In Java, if we have two different objects of the same class, and if we assign the reference of one object to the other reference of the other object, then they refer to the same object.

The difference here is in the definition of reference. What Java refer to as a reference C++ would refer to as a pointer (as they always refer to dynamically allocated objects that last beyond the current scope). Though the Java reference is smart and the object is garbage collected when all "Java references" are gone. So a Java reference would be more equivalent to a std::shared_ptr<> .

// Java                       // C++
String s = new String("S");   std::shared_ptr<std::string> s = new std::string("S");

C++ automatic objects are more like Java "value types" (int/char/float). The difference is that in C++ any type can act like a "value type".

// Java                       // C++
int      x = 12;              int    x = 12;
                              Test   t1(10);  // acts like an int value
                                              // except destructor called
                                              // when it goes out of scope.

Afterward, changing a data member by one reference would change the data member in the other reference as well.

Yes basically. But you have to be careful of your wording. If the member is a reference then you are not changing this member (as references never change what they refer to in C++) you are changing the object that the reference is referring to.

// Java
class MyType  // forgive my syntax.
{
     public MyType() {x = new MyOtherType(); y = x}
     public MyOtherType x;
     public MyOtherType y;
};

MyType       a = new MyType();
MyOtherType  b = new MyOtherType();

a.x = b;

// Here both a.x and b refer to the same object.
// While a.y will refer to the original value.
// That is not how C++ will work (if you are thinking reference like
// like your description).


// C++
class MyType
{
     public:
         MyType() : x(), y(x) {}
         MyOtherType  x;
         MyOtherType& y;
          // Note   ^
};


MyType       a;
MyOtherType  b;

a.x = b;

// Here a.x and b are still completely distinct objects.
// The content of b have been copied to a.x and 
// thus accessible from a.y

My question is: Isn't it the same in C++ too?

No. Because references are not objects. You need to think of Java references being equivalent to a C++ std::shared_ptr. This is how we manage dynamically allocated memory in C++.

I got a bit confused about Copy constructors and assignment operators. Both of them do deep copy. Without them, we can only do shallow copy as far as I know. I have a code snippet too.

Basically true. It basically allows you to define how assignment works correctly when copying one object into another object.

Lets comment the code.
The output you describe is the output I would expect.

#include <iostream>
using namespace std;          // Please stop doing this.

Test

class Test
{
    int x;
    int &ref;

    public:
        Test(int i)
            : x(i)            // Set this->x = i
                              // Note X is an automatic object.
            , ref(x)          // Set this->ref as an alias for this->x
                              // Note1: This can never be undone.
                              // Note2: Changing ref will always change x
                              //        conversely changing x will be
                              //        visible via ref.
                              // Note3: ref can **not** be made to reference
                              //        another object.
        {}
        void print()
        {
            cout << ref;      // Prints what this->ref is a reference too:
                              // Looking at the constructor its this->x
        }
        void setX(int i)
        {
            x = i;            // Modifies this->x
                              // This is visible via this->ref
        }
        Test &operator = (const Test &t)
        {
            x = t.x;          // Copies the value from t.x into this->x
                              // Note1: this->ref and t.ref continue to refer
                              //        to their origin objects.
                              // Note2: since this->x has changed
                              //        this change is visible in this->ref
            return *this;
        }
};

Main

int main()
{
    Test t1(10);                  // t1.x = 10 t1.ref = t1.x
    Test t2(20);                  // t2.x = 20 t2.ref = t2.x
    t2 = t1;                      // Assignment operator above called.
                                  // t2.x = 10 t2.ref = t2.x

    t1.setX(40);                  // Call the setX method on t1
                                  // t1.x = 40 t1.ref = t1.x

    t2.print();                   // Call the print method on t2
                                  // This prints t2.ref to the output.
                                  // t2.ref is a reference to t2.x
                                  // So we print the value t2.x
                                  // Should print 10

    cout << "\n\n";

    t1.print();                   // Call the print method on t1
                                  // This prints t1.ref to the output.
                                  // t1.ref is a reference to t1.x
                                  // So we print the value t1.x
                                  // Should print 40

    return 0;
}

When you create the t2 instance, t2.ref is initialized to refer to t2.x :

Test(int i):x(i), ref(x) {}

Latter,

t2 = t1;

does not change what t2.ref refers to (as it would be invalid: in C++ a reference cannot bind to a new referent):

Test &operator = (const Test &t) {x = t.x; return *this;}

t2.ref still refers to t2.x , whose value you never alter:

t1.setX(40);

In Java, you have two kinds of objects: those always passed by value, which are exclusively the native data types: int, long, double, ... and the non-native ones, implicitly inheriting from Object , which are stored in variables as references and passed on as such on assignment (eg to other variables or function parameters).

In C++, any data type can show both types of behaviour, being passed by value or passed by reference. And in C++, you have to be explicit about.

In C++, you have three ways, not just two, how you can pass on objects:

  • by value
  • by reference
  • by pointer

Java references are far closer to C++ pointers than to C++ references, though: You can re-assign pointers to different objects, you cannot do so for references. It helps considering C++ references just as an alias (another name) for a concrete object, ignoring that under the hoods this might really apply in some cases (then no memory is allocated for at all) and in others (especially function parameters) not.

Now to be precise: Even in Java every variable is copied, even the reference variables. But for the latter, it is only the reference itself that is copied, not the object! So to show up the parallels:

// Java                         // C++

int n = 10;                     int n = 10;
String s = "hello";             std::string* s = new std::string("hello");

f(n, s);                        f(n, s);
voidf(int n, String s)          void f(int n, std::string* s)
{                               {
    n = 12;                          n = 12; // in both cases, only local variable changed
    s += "ads";                     *s += "ads";
                                //  ^ solely: special syntax for pointers!
    s = "world";                    s = new std::string("world");
   // now s refers/points to another object in both cases
}                               }

Should be known so far... To be honest, above is simplified too much : In Java, we have automatic garbage collection, in C++, we don't. So actually, the string objects created above in C++ are never cleaned up any more and we have a memory leak! Modern C++ introduced smart pointers to facilitate the issue, consider the garbage collection in Java as well, the C++ code might rather look like:

int n;
std::shared_ptr<std::string> s = std::make_shared("hello");
f(n, s);

void f(int n, std::shared_ptr<std::string> s)
{
    n = 12;
    *s += "ads";
    s = std::make_shared("world");
}

where std::shared_ptr does all the reference counting that in Java is hidden in the Object base class...

Unlike in Java, you can pass the string by value, too, though:

void f(int, std::string s);

Now the string will be copied into the local variable, just like the int. Solely: How???

That's where the copy constructor comes into play. If you want so, it is a recipe for how to create that copy, ie how to copy the internal representation into the new object. For a std::string , this would be some internal, dynamically allocated array holding the string's contents, its current capacity (ie total length of the array) and its size (how much of the array actually is occupied).

Finally the C++ reference:

void f(int, std::string& s);

In this case, s will always refer to the object that was assigned to the function, you cannot change the reference (at least not legally, in some cases you could with some really dirty hacks...), only the referred object.

Actually, in C++ the issue is more complex:

In Java, you get automatic memory management, there's a garbage collector selecting all objects that aren't referred to any more. Such thing doesn't exist in C++, we need to care for cleanup manually. Actually, the strings I created in the sample above aren't cleaned up at all, so we got a memory leak!!!

A more modern way is

In his book on C++ Stroustrup has explained this thus:

Note that the two objects are independent. We can change the value of y without affecting the value of x. For example x=99 will not change the value of y. Unlike Java, C#, and other languages, but like C, that is true for all types, not just for ints. If we want different objects to refer to the same (shared) value, we must say so. We could use pointers:

int x = 2;
int y = 3;
int∗ p = &x;
int∗ q = &y; // now p!=q and *p!=*q
p = q; // p becomes &y; now p==q, so (obviously)*p == *q

架构

I arbitrarily chose 88 and 92 as the addresses of the ints. Again, we can see that the assigned-to object gets the value from the assigned object, yielding two independent objects (here, pointers), with the same value. That is, p=q gives p==q. After p=q, both pointers point to y. A reference and a pointer both refer/point to an object and both are represented in memory as a machine address. However, the language rules for using them differ. Assignment to a reference does not change what the reference refers to but assigns to the referenced object:

int x = 2;
int y = 3;
int& r = x; // r refers to x
int& r2 = y; // now r2 refers to y
r = r2; // read through r2, write through r: x becomes 3

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM