3

The code below shows a class representing complex numbers. My interest is in understanding the operator+ function. As I understand Complex res should be allocated on the frame of the function operator+. Is it correct to return this object to the caller? By the time this function returns the frame would have been popped but res would continue to be used by the caller. Unless there is more to this than meets the eye, like the actual return res may actually be copying the object from current frame to the caller's frame. Another possibility can be that the code inside the operator+ function may be inlined on the call site in main? From my limited understanding of the language, functions declared within the class are by default inlined on the call site. Any help will be appreciated.

#include<iostream>
using namespace std;

class Complex {
private:
    int real, imag;
public:
    Complex(int r = 0, int i =0) {real = r; imag = i;}
    
    Complex operator+(Complex const &obj) {
        Complex res;
        res.real = real + obj.real;
        res.imag = imag + obj.imag;
        return res;
    }
    void print() { cout << real << " + i" << imag << endl; }
};

int main()
{
    Complex c1(10, 5), c2(2, 4);
    Complex c3 = c1 + c2; 
    c3.print();
}

PORTION BELOW ADDED TO CLARIFY THE SOLUTION AFTER READING THE COMMENTS AND ANSWERS BELOW

I updated the code above with the following:

#include<iostream>
using namespace std;

class Complex {
private:
    int real, imag;
public:
    Complex(int r = 0, int i =0) {real = r; imag = i;}
    
    Complex operator+(Complex const &obj) {
        Complex res;
        res.real = real + obj.real;
        res.imag = imag + obj.imag;
        cout << "Address inside the function " << &res << "\n";
        return res;
    }
    void print() { cout << real << " + i" << imag << endl; }
};

int main()
{
    Complex c1(10, 5), c2(2, 4);
    Complex c3 = c1 + c2; 
    cout << "Address outside the function " << &c3 << "\n";
    c3.print();
}

The output shows two different addresses on two different regions of the stack indicating copy by value during return:

Address inside the function 0x7fffbc955610
Address outside the function 0x7fffbc955650
sshekhar1980
  • 327
  • 2
  • 12
  • Try not to think about frames when learning C++. It is a high-level language, and concept of a frame is not part of it. While it might be important to understand this when you have mastered the language, and want to study some practical aspects, such as performance, at this level it is only going to confuse you. – SergeyA May 20 '21 at 15:53
  • 1
    You are returning Complex by value from the operator+() function. So this will be a copy of res. – DS_London May 20 '21 at 15:55
  • @DS_London thanks a bunch! I come from Java background and I cannot think beyond references. I modified the code above with the following: Inside `operator+` I added the following line `cout << "Address inside the function " << &res << "\n";` Inside `main` I added the following line `cout << "Address outside the function " << &c3 << "\n";`. I got two different addresses indicating the **copy by value** you mentioned. – sshekhar1980 May 20 '21 at 16:06
  • 1
    Np. You might find it interesting to put an additional output line within the Complex() default constructor you have written, and see how many times it is called. You will see that while you have 4 Complex objects, your constructor is only called 3 times. The return value from the operator() function uses a different constructor (copy or move), which you haven't defined but has been implemented for you. If you define the copy constructor Complex(const Complex & c){} you will see that it gets called for the return value. How & when these other constructors get created is slightly involved ... – DS_London May 20 '21 at 16:28

2 Answers2

1

Is it correct to return this object to the caller?

C++ supports both return by reference and return by value. Since you are not using return by reference, you are not returning a reference to the object to the caller. You are using return by value, so you are returning the object's value to the caller. Consider:

int foo()
{
    int i = 2;
    return i;
}

This returns the value 2. It does not return the object i. That i itself no longer exists after the return doesn't matter because its value has already been used to determine the value returned.

David Schwartz
  • 179,497
  • 17
  • 214
  • 278
1

Transfer with value always uses stack. When you want to return a value, depending on the caller code, the copy constructor or assignment operator may implicitly call and assign the return value to the object on the left. (lvalue)

Complex  nwobj=cmpx1 + cmplx2; //copy constructor used to assign return object to lvalue

cmplx3=cmplx1+xmplx2;//operator= used to make a right assignment. 

Note:

The copy construction in the first line may happen or may be elided depending on the used compiler and its settings. A comprehensive explanation about this can be found in:
SO: What are copy elision and return value optimization?

Scheff's Cat
  • 19,528
  • 6
  • 28
  • 56
Alireza
  • 15
  • 5
  • `Complex nwobj=cmpx1 + cmplx2; //copy constructor used to assign return object to lvalue` This is not necessarily true. In full trust of [copy elision and return value optimization](https://stackoverflow.com/a/12953129/7478597), I made a [**Demo on coliru**](http://coliru.stacked-crooked.com/a/e7db4adeeb017343) which illustrates the opposite. Please, note that there are exactly three constructor calls, and none of them is the copy constructor. – Scheff's Cat May 20 '21 at 16:51
  • @Scheff If I define `Complex(const Complex&){}` in the OP's code, then it does get called in the line Complex c3 = c1 + c2. If I also define `Complex(Complex&&)` that gets called instead. If I amend the `operator+` definition to `return Complex(real + obj.real, imag + obj.imag);` then neither gets called (I assume because of optimization). – DS_London May 20 '21 at 17:02
  • I did define `Complex(const Complex&)` and didn't forget a "diagnostic" output with `std::cout`. (Btw. there are also exactly 3 destructor calls.) The question is, what compiler (with what C++ standard) did you use? I used `g++ -std=c++17`. (In C++17, copy elision became mandatory.) I forgot to mention this... ;-) – Scheff's Cat May 20 '21 at 17:06
  • And just to be sure, I added move constructor and move assignment although I already knew that this wouldn't change anything: [**Demo (with move ctor) on coliru**](http://coliru.stacked-crooked.com/a/1bf9f3a744c744da) – Scheff's Cat May 20 '21 at 17:08
  • If you're not yet convinced, I print also the address of the local `res` in `Complex::operator+()`, and it's just the same like the one of `c3`: [**Demo on coliru**](http://coliru.stacked-crooked.com/a/fbb577400e77f113) – Scheff's Cat May 20 '21 at 17:25
  • @Scheff How is the new object returned from the operator+ assigned in the following code? Complex nwobj = cmpx1 + cmplx2; – Alireza May 20 '21 at 18:18
  • That's the trick with copy elision: The compiler realizes that the result `res` has to be returned and is then stored into another object which is just in construction. Hence, `res` is constructed into the allocated memory for the return value, which in turn is at the address of `c3`, and the copying is eliminated. – Scheff's Cat May 20 '21 at 18:31
  • yes..compiler directly create object on rvalue!!am i right?this is a optimization and depend to compiler config and flag set – Alireza May 20 '21 at 18:33
  • Not sure about the RValue, maybe. What I'm sure about: the `return` value needs storage in any case. The first optimization is, that `res` get its storage at the address of that `return` value. Hence, copying of the local to the return value is eliminated. (That's Return Value Optimization.) The other optimization happens on the call side: The initialization prevents the copying and just passes the address of the object in construction as address of the return value to the function call. (That's copy elision.) – Scheff's Cat May 20 '21 at 18:39
  • I meant lvalue .. I was wrong, sorry.In fact, some additional operations are eliminated with this method. Create a new object. Copy of object ot lvalue – Alireza May 20 '21 at 18:43
  • The more precise explanation can be found on cppreference.com: [Copy elision](https://en.cppreference.com/w/cpp/language/copy_elision) – Scheff's Cat May 20 '21 at 18:43
  • @Scheff Yes, I am studying it right now. I already knew something about optimizing returns. thank you. I just registered on this site a few hours ago – Alireza May 20 '21 at 18:49
  • @Scheff Using MSVC 19, but not sure what default c++ version is. With the OP’s code and just the addition of the copy/move constructor, I get 4 constructor calls (3xdefault & 1xcopy/move). – DS_London May 20 '21 at 21:11
  • @DS_London Did the copy constructor call for the following code?Complex nwobj=cmpx1 + cmplx2; – Alireza May 21 '21 at 03:13
  • @DS_London So far, the differences between OPs code and my sample code are IMHO not that relevant but you may copy/paste my sample and check it as well. (I'm not aware of any significant difference but I may have overlooked something.) I tried my last demo with `g++ -std=c++11` and didn't get a difference. I get one if I explicitly suppress copy elision with `g++ -fno-elide-constructors`: [**Demo on coliru**](http://coliru.stacked-crooked.com/a/732dafbd329a9789) (I also tried using `-g` instead of `-O2` but then I got copy elision - even with `-std=c++11`). – Scheff's Cat May 21 '21 at 05:18
  • @DS_London If you're using VS2019 it may depend on `Debug` vs `Release`. Additionally, I'm not sure what the current default standard of MSVC is. (I'm using CMake for the configurations of solution/projects and force C++17 always. The respective compiler command line option is `/std:c++17`, and in case also `/permissive-` which seems to be similar to the `-pedantic` of `g++`.) – Scheff's Cat May 21 '21 at 05:23
  • @DS_London I tried my last demo on my local box in VS2019 (updated to the last version a few days ago). I tried without and with `/std:c++17` and added `/permissive-` to the latter. The result is always the same: In `Debug`, I get an additional copy construction. In `Release`, it's elided. – Scheff's Cat May 21 '21 at 05:38
  • @Scheff We are on the same page at last! I just started a new default project, which (as I now know) used C++14. Anyhow, if I switch from Debug to Release I lose the copy/move constructor call. Same if I specify C++17. I learned about C++ constructors in the mid-90s, and clearly my knowledge hasn't kept up! Many thanks for the example. – DS_London May 21 '21 at 12:23