What is the difference between these two constructors that are doing the same thing?

  • Thread starter yungman
  • Start date
  • #71
yungman
5,644
227
I am reviewing math operators overloading. I have a question about math overloading operator xx (). Say I want to have a return of class object what is the difference between:
C++:
class Test;
Type A,B;
// operator xx where xx can be +, -, *, / etc.
Test& operator xx ( const Test &rhs){Test result; ...  ; return result;}//declare return type as reference.
Test operator xx ( const Test &rhs){Test result; ...  ; return result;}//declare return type is Test Object.

I actually tried both, seems like both return object result even though line 3 claims return type is a reference. I don't understand this. Can anyone elaborate this a little?

Thanks
 
  • #72
Jarvis323
1,049
952
I am reviewing math operators overloading. I have a question about math overloading operator xx (). Say I want to have a return of class object what is the difference between:
C++:
class Test;
Type A,B;
// operator xx where xx can be +, -, *, / etc.
Test& operator xx ( const Test &rhs){Test result; ...  ; return result;}//declare return type as reference.
Test operator xx ( const Test &rhs){Test result; ...  ; return result;}//declare return type is Test Object.

I actually tried both, seems like both return object result even though line 3 claims return type is a reference. I don't understand this. Can anyone elaborate this a little?

Thanks
A reference is like a pointer. Under the hood, it only is the address you're returning. You don't want to return a reference to a destroyed object.
 
  • #73
yungman
5,644
227
A reference is like a pointer. Under the hood, it only is the address you're returning. You don't want to return a reference to a destroyed object.
Thanks for the reply, seems like the book always return a reference!
I think it is always used in the case:
C++:
Test C;
C = A + B;

result is going to be destroy right after it gets passed to C and is out of scope. I remember tracing programs and the result got destroy almost right away. So does it matter either case be it as reference or as object. This is part I'm still not clear.

Thanks
 
  • #74
Jarvis323
1,049
952
result will be destroyed when it goes out of scope. That's at the end of the function at the }. So the returned reference will be invalid. So you need to return a value in that case.

You can return a member variable by reference because it still exists as part of the class instance.
 
  • #75
yungman
5,644
227
result will be destroyed when it goes out of scope. That's at the end of the function at the }. So the returned reference will be invalid. So you need to return a value in that case.

You can return a member variable by reference because it still exists as part of the class instance.
Thank you. I put it in my notes.
 
  • #76
yungman
5,644
227
I am still reading Copy Constructor and Assignment overloading operator. I want to confirm this:

C++:
class Test{};
Test A;
Test B = A;// Using Copy Constructor only because B is being created and copy A over.
Test C;
C = A;//This uses Assignment overload operator, NOT Copy Constructor as C already exist.

I put my comment in the program. I want to verify I am right.

Thanks
 
  • #77
jbunniii
Science Advisor
Homework Helper
Insights Author
Gold Member
3,475
257
The above is correct.
Code:
Test B = A; // calls copy constructor; equivalent to Test B(A);
Test C; C = A; // calls copy assignment operator
 
  • #78
yungman
5,644
227
The above is correct.
Code:
Test B = A; // calls copy constructor; equivalent to Test B(A);
Test C; C = A; // calls copy assignment operator
Thanks

I just want to make sure you said copy assignment operator, that is void operator=(const Test&rhs).
Of cause if I want to return something, it would be Test operator=(const Test&rhs)

Thanks
 
  • #79
jbunniii
Science Advisor
Homework Helper
Insights Author
Gold Member
3,475
257
Thanks

I just want to make sure you said copy assignment operator, that is void operator=(const Test&rhs).
Of cause if I want to return something, it would be Test operator=(const Test&rhs)

Thanks
A properly defined copy assignment operator must always return a non-const reference to *this. This is necessary in order for the usual assignment semantics to work, for example a = b = c; would result in a compilation error if your assignment operator doesn't return anything.

So the declaration should be Test& operator=(const Test& rhs);

The standard implementation is

Code:
Test& operator=(const Test& rhs)
{
    if (&rhs == this) {
        return *this;
    }
    // example assuming This has two member variables:
    //    int someInt;
    //    std::string someString;
    someInt = rhs.someInt;
    someString = rhs.someString;
    return *this;
}

Btw, I said copy assignment operator instead of just assignment operator, because there's also a move assignment operator, which you declare as Test& operator=(Test&& rhs);
 
  • #80
yungman
5,644
227
A properly defined copy assignment operator must always return a non-const reference to *this. This is necessary in order for the usual assignment semantics to work, for example a = b = c; would result in a compilation error if your assignment operator doesn't return anything.

So the declaration should be Test& operator=(const Test& rhs);

The standard implementation is

Code:
Test& operator=(const Test& rhs)
{
    if (&rhs == this) {
        return *this;
    }
    // example assuming This has two member variables:
    //    int someInt;
    //    std::string someString;
    someInt = rhs.someInt;
    someString = rhs.SomeString;
    return *this;
}

Btw, I said copy assignment operator instead of just assignment operator, because there's also a move assignment operator, which you declare as Test& operator=(Test&& rhs);

A properly defined copy assignment operator must always return a non-const reference to *this. This is necessary in order for the usual assignment semantics to work, for example a = b = c; would result in a compilation error if your assignment operator doesn't return anything.

So the declaration should be Test& operator=(const Test& rhs);

The standard implementation is

Code:
Test& operator=(const Test& rhs)
{
    if (&rhs == this) {
        return *this;
    }
    // example assuming This has two member variables:
    //    int someInt;
    //    std::string someString;
    someInt = rhs.someInt;
    someString = rhs.someString;
    return *this;
}

Btw, I said copy assignment operator instead of just assignment operator, because there's also a move assignment operator, which you declare as Test& operator=(Test&& rhs);
Thanks for the reply.

I was just talking about Test& operator=(const Test& rhs) vs Test operator=(const Test& rhs);
I thought I should use the second one to return the object instead of reference of the object as talked in post 72 onward. Which way is right?

Thanks
 
  • #81
jbunniii
Science Advisor
Homework Helper
Insights Author
Gold Member
3,475
257
Thanks for the reply.

I was just talking about Test& operator=(const Test& rhs) vs Test operator=(const Test& rhs);
I thought I should use the second one to return the object instead of reference of the object as talked in post 72 onward. Which way is right?

Thanks
The first one is right. The second one will also work if Test is copyable, but will result in unnecessary extra work.

Consider the assignment chain a = b = c;

If your assignment operator returns a reference (Test&), then the above chain will result in two calls to the assignment operator, as you would expect.

On the other hand, if your assignment operator returns a value (Test), then the above chain will result in two calls to the assignment operator, plus two calls to the copy constructor (in order to return temporary unnamed copies of b and a), plus two calls to the destructor (in order to destroy the temporary unnamed copies).
 
  • #82
jbunniii
Science Advisor
Homework Helper
Insights Author
Gold Member
3,475
257
Your example in post #72 is different. Copying it here for convenience:
Code:
Test& operator xx ( const Test &rhs){Test result; ...  ; return result;}//declare return type as reference.
Test operator xx ( const Test &rhs){Test result; ...  ; return result;}
Here, your operator xx is creating a local Test object and returning it. You have no choice but to return a copy (the second implementation). If you return a reference (the first implementation), then the object (result) to which that reference points will be destroyed before the caller can use it. This error is called a dangling reference.

But for the assignment operator, you're returning a reference to *this, which is not a local variable and still exists after the assignment operator exits. In particular, a correctly implemented assignment operator does not return a local variable (like your Test result; in the example above).

Your example above is more typical of binary operators that create a new object given two objects, such as operator+.
 
  • #83
yungman
5,644
227
Your example in post #72 is different. Copying it here for convenience:
Code:
Test& operator xx ( const Test &rhs){Test result; ...  ; return result;}//declare return type as reference.
Test operator xx ( const Test &rhs){Test result; ...  ; return result;}
Here, your operator xx is creating a local Test object and returning it. You have no choice but to return a copy (the second implementation). If you return a reference (the first implementation), then the object (result) to which that reference points will be destroyed before the caller can use it. This error is called a dangling reference.

But for the assignment operator, you're returning a reference to *this, which is not a local variable and still exists after the assignment operator exits. In particular, a correctly implemented assignment operator does not return a local variable (like your Test result; in the example above).

Your example above is more typical of binary operators that create a new object given two objects, such as operator+.
Thanks so much

I actually wrote a program for both cases to proof using Test&operator() doesn't work.
C++:
#include <iostream>
using namespace std;
const int Nsize = 51;
class Test1//Passing  as reference, not working.
{public:
    char* name;
    int x;
    Test1()
    {
        name = new char[Nsize];strncpy_s(name, Nsize, "blank", Nsize);
    }
    Test1(const char*desc, int x0)
    {
        name = new char[Nsize];strncpy_s(name, Nsize, desc, Nsize);x = x0;
    }
    Test1(const Test1& right)
    {
        name = new char[Nsize];strncpy_s(name, Nsize, right.name, Nsize);
        x = right.x;
    }
    Test1& operator = (const Test1&rhs)//Passing as reference
    {
        x = rhs.x; return *this;
    }
    Test1& operator+(const Test1& rhs)//Passing as reference
    {
        Test1 sum; sum.x = x + rhs.x; return sum;
    }
    void print()
    {
        cout << name << " = (" << name << ", " << x << ")\n\n";
    }
    ~Test1()
    {
        delete[] name;
    }
};
class Test2//Passing as Object, works.
{public:
    char* name;
    int x;
    Test2()//Constructor
    {
        name = new char[Nsize];    strncpy_s(name, Nsize, "blank", Nsize);
    }
    Test2(const char* desc, const int x0)//Constructor
    {
        name = new char[Nsize];    strncpy_s(name, Nsize, desc, Nsize);
        x = x0;  
    }
    Test2(const Test2& right)//Copy constructor
    {
        name = new char[Nsize];
        strncpy_s(name, Nsize, right.name, Nsize);
        x = right.x;
    }
    Test2 operator = (const Test2& rhs)
    {
        x = rhs.x;
        return *this;  
    }
    Test2 operator+(const Test2& rhs)
    {
        Test2 sum;
        sum.x = x + rhs.x;  
        return sum;
    }
    void print()
    {
        cout << name << " = (" << name << ", " << x << ")\n\n";
    }
    ~Test2()
    {
        delete[] name;
    }
};
int main()
{
    Test1 A("B", 0);
    Test1 B("B", 1);
    Test1 C("C", 2);
    A = B + C; 
    A.print();
    Test2 D("D", 0);
    Test2 E("E", 3);
    Test2 F("F", 4);
    D = E + F; 
    D.print();
}

I stepped through the program in debug and actually saw sum got destroy before copying over as it went out of scope and got garbage in class Test1.

Thanks
 
Last edited:
  • #84
yungman
5,644
227
To the others that said I should space the code out like the program above. I find it very hard to read. Just look at how long the program gets, I have to scroll up and down to read, it is so easy to lost track. I much much prefer to read program like this:
C++:
#include <iostream>
using namespace std;
const int Nsize = 51;
class Test1
{public: char* name; int x;
    Test1()    {name = new char[Nsize];strncpy_s(name, Nsize, "blank", Nsize);}
    Test1(const char*desc, int x0){name = new char[Nsize];strncpy_s(name, Nsize, desc, Nsize);x = x0;}
    Test1(const Test1& right){name = new char[Nsize];strncpy_s(name, Nsize, right.name, Nsize);    x = right.x;}
    Test1& operator = (const Test1&rhs){x = rhs.x; return *this;}
    Test1& operator+(const Test1& rhs){Test1 sum; sum.x = x + rhs.x; return sum;    }
    void print(){cout << name << " = (" << name << ", " << x << ")\n\n";}
    ~Test1(){delete[] name;    }
};
class Test2
{public: char* name; int x;
    Test2()//Constructor
    {    name = new char[Nsize];    strncpy_s(name, Nsize, "blank", Nsize);    }
    Test2(const char* desc, const int x0)//Constructor
    {name = new char[Nsize];    strncpy_s(name, Nsize, desc, Nsize);x = x0;    }
    Test2(const Test2& right)//Copy constructor
    {name = new char[Nsize];    strncpy_s(name, Nsize, right.name, Nsize);x = right.x;}
    Test2 operator = (const Test2& rhs){x = rhs.x;return *this;    }
    Test2 operator+(const Test2& rhs){    Test2 sum;    sum.x = x + rhs.x;    return sum;    }
    void print(){cout << name << " = (" << name << ", " << x << ")\n\n";}
    ~Test2(){delete[] name;    }
};
int main()
{ Test1 A("B", 0);    Test1 B("B", 1);Test1 C("C", 2);
    Test2 D("D", 0);Test2 E("E", 3);Test2 F("F", 4);
    A = B + C;    A.print();
    D = E + F;    D.print();
}

But if I ask question here, I will honor the request, to space them out before posting.
 
  • #85
jbunniii
Science Advisor
Homework Helper
Insights Author
Gold Member
3,475
257
Thanks so much

I actually wrote a program for both cases to proof using Test&operator() doesn't work.
C++:
#include <iostream>
using namespace std;
const int Nsize = 51;
class Test1//Passing  as reference, not working.
{public:
    char* name;
    int x;
    Test1()
    {
        name = new char[Nsize];strncpy_s(name, Nsize, "blank", Nsize);
    }
    Test1(const char*desc, int x0)
    {
        name = new char[Nsize];strncpy_s(name, Nsize, desc, Nsize);x = x0;
    }
    Test1(const Test1& right)
    {
        name = new char[Nsize];strncpy_s(name, Nsize, right.name, Nsize);
        x = right.x;
    }
    Test1& operator = (const Test1&rhs)//Passing as reference
    {
        x = rhs.x; return *this;
    }
    Test1& operator+(const Test1& rhs)//Passing as reference
    {
        Test1 sum; sum.x = x + rhs.x; return sum;
    }
    void print()
    {
        cout << name << " = (" << name << ", " << x << ")\n\n";
    }
    ~Test1()
    {
        delete[] name;
    }
};
class Test2//Passing as Object, works.
{public:
    char* name;
    int x;
    Test2()//Constructor
    {
        name = new char[Nsize];    strncpy_s(name, Nsize, "blank", Nsize);
    }
    Test2(const char* desc, const int x0)//Constructor
    {
        name = new char[Nsize];    strncpy_s(name, Nsize, desc, Nsize);
        x = x0; 
    }
    Test2(const Test2& right)//Copy constructor
    {
        name = new char[Nsize];
        strncpy_s(name, Nsize, right.name, Nsize);
        x = right.x;
    }
    Test2 operator = (const Test2& rhs)
    {
        x = rhs.x;
        return *this; 
    }
    Test2 operator+(const Test2& rhs)
    {
        Test2 sum;
        sum.x = x + rhs.x; 
        return sum;
    }
    void print()
    {
        cout << name << " = (" << name << ", " << x << ")\n\n";
    }
    ~Test2()
    {
        delete[] name;
    }
};
int main()
{
    Test1 A("B", 0);
    Test1 B("B", 1);
    Test1 C("C", 2);
    A = B + C;
    A.print();
    Test2 D("D", 0);
    Test2 E("E", 3);
    Test2 F("F", 4);
    D = E + F;
    D.print();
}

I stepped through the program in debug and actually saw sum got destroy before copying over as it went out of scope and got garbage in class Test1.

Thanks
On an unrelated note, I noticed that your no-argument constructors (Test1() and Test2()) are failing to initialize the member variable x, and since you don't have a default initializer for x, it will be uninitialized, hence will probably contain garbage. In general it's a very good practice to provide a default initializer for any member variable that is of a type (such as int) that would otherwise be uninitialized. Example:
Code:
class Test1//Passing  as reference, not working.

{public:

    char* name = ""; // default initialization
    int x = 0; // default initialization

    Test1()
    {
        name = new char[Nsize];
        strncpy_s(name, Nsize, "blank", Nsize);
        // x will have the default-initialized value of zero since
        // this constructor doesn't assign a value to x
    }

    Test1(const char*desc, int x0)
    {
        name = new char[Nsize];
        strncpy_s(name, Nsize, desc, Nsize);
        x = x0;
    }
 
  • #86
jbunniii
Science Advisor
Homework Helper
Insights Author
Gold Member
3,475
257
To the others that said I should space the code out like the program above. I find it very hard to read. Just look at how long the program gets, I have to scroll up and down to read, it is so easy to lost track. I much much prefer to read program like this:

...

But if I ask question here, I will honor the request, to space them out before posting.
It's up to you how to format code that only you will read, but it's definitely a good practice to use a reasonably standard code style (one statement per line, use whitespace to improve readability, etc.) for any code that others will read. That includes this forum, but also and more importantly any code that will be read or maintained by others, e.g. in an open-source project or for an employer or customer.

For a good example of the style generally used by professional C++ developers, you can take a look at Google's C++ style guide: https://google.github.io/styleguide/cppguide.html
 
  • #87
jbunniii
Science Advisor
Homework Helper
Insights Author
Gold Member
3,475
257
One more thing I just noticed, your operator= is only assigning x, not name.
Code:
    Test1& operator = (const Test1&rhs)//Passing as reference
    {
        x = rhs.x;
        return *this;
    }
This is not illegal, but it will certainly result in unexpected behavior, e.g.:
Code:
Test1 t1("a", 1);
Test2 t2("b", 2);

// One would reasonably expect the following to result in
//   t1.name = "b"  and  t1.x = 2
// but instead it will result in
//   t1.name = "a"  and  t1.x = 2
t1 = t2;
A more natural implementation would be
Code:
    Test1& operator=(const Test1& rhs)
    {
        if (&rhs != this) {
            x = rhs.x;
            strncpy_s(name, Nsize, rhs.name, Nsize);
        }
        return *this;
    }
 
  • #88
36,871
8,916
To the others that said I should space the code out like the program above. I find it very hard to read. Just look at how long the program gets, I have to scroll up and down to read, it is so easy to lost track.
It's worse when you pack two or three statements on a line -- it's easy to miss them. Plus, the debugger can't stop at one of those individual statements.
I much much prefer to read program like this:
You're pretty much alone in this. Most of us who have been doing this for years really dislike the packed format that you favor -- it's really easy to miss something important.
t's up to you how to format code that only you will read, but it's definitely a good practice to use a reasonably standard code style (one statement per line, use whitespace to improve readability, etc.) for any code that others will read.
I agree 100%. If it's code that only @yungman will read, then whatever floats his boat is fine. But if he posts it here, please do the rest of us a favor and space it out. We don't mind scrolling down through the code.
 
  • Like
Likes jbunniii and Vanadium 50
  • #89
yungman
5,644
227
It's worse when you pack two or three statements on a line -- it's easy to miss them. Plus, the debugger can't stop at one of those individual statements.
You're pretty much alone in this. Most of us who have been doing this for years really dislike the packed format that you favor -- it's really easy to miss something important.
I agree 100%. If it's code that only @yungman will read, then whatever floats his boat is fine. But if he posts it here, please do the rest of us a favor and space it out. We don't mind scrolling down through the code.
That's what I said, I post with the code spaced out like in post 83. I just said for me, I rather read packed code. It doesn't matter to anyone if I space them out before I post. I can honor that. In my notes, I like different color in my own notes, because I can find the name in other part of the program easier and also not drop out from one line to the other.
One more thing I just noticed, your operator= is only assigning x, not name.
Code:
    Test1& operator = (const Test1&rhs)//Passing as reference
    {
        x = rhs.x;
        return *this;
    }
This is not illegal, but it will certainly result in unexpected behavior, e.g.:
Code:
Test1 t1("a", 1);
Test2 t2("b", 2);

// One would reasonably expect the following to result in
//   t1.name = "b"  and  t1.x = 2
// but instead it will result in
//   t1.name = "a"  and  t1.x = 2
t1 = t2;
A more natural implementation would be
Code:
    Test1& operator=(const Test1& rhs)
    {
        if (&rhs != this) {
            x = rhs.x;
            strncpy_s(name, Nsize, rhs.name, Nsize);
        }
        return *this;
    }


Yes, actually I did that already, I originally had the name copied over in the Copy Constructor. Actually dealing with the name is the main reason I created this thread. Here I want to keep the name in A and D as 'A' and 'D' resp., so I decided to removed
C++:
strncpy_s(name, Nsize, rhs, Nsize)
and leave the original name alone in A and D. It is intentional on my part.

Thanks
 
  • #90
yungman
5,644
227
It's worse when you pack two or three statements on a line -- it's easy to miss them. Plus, the debugger can't stop at one of those individual statements.
You're pretty much alone in this. Most of us who have been doing this for years really dislike the packed format that you favor -- it's really easy to miss something important.
I agree 100%. If it's code that only @yungman will read, then whatever floats his boat is fine. But if he posts it here, please do the rest of us a favor and space it out. We don't mind scrolling down through the code.
Like I said, I will space the code out like in post #73. I just commented I find it easier to read when putting all the lines together so I don't have to scroll up and down. To each their own, I will honor the forum here and space it out.
 

Suggested for: What is the difference between these two constructors that are doing the same thing?

Replies
1
Views
360
Replies
10
Views
1K
Replies
36
Views
2K
Replies
18
Views
489
Replies
5
Views
705
Replies
29
Views
1K
Replies
16
Views
643
Top