Operator Overloading : Return by Reference

For some of the binary operators, if we wish to have chained functionality we return the object to support it.Below is one such example:

1
2
3
4
5
ostream& operator<< (ostream &out, int & a) //friend function
{
    out << a.val;
    return out;
}


My question is: Can we achieve the same purpose by returning by value instead of reference? From what I understood, we are returning by reference only for efficiency (to avoid duplicate objs)
You can't copy a stream object so returning by value is impossible here.
If copying was possible and return by value was used you would get slicing problems if the stream object is an instance of a subclass of ostream.
Last edited on
could you please elaborate on what you mean by slicing problems?

Also how about in the case of an assignment operator? In the below example can I return by value?

1
2
3
4
5
6
7
8
Cents& Cents::operator= (const Cents &cSource)
{
    // do the copy
    m_nCents = cSource.m_nCents;
 
    // return the existing object
    return *this;
}
istream objects all have their copy constructors and assignment operators hidden, meaning that you can't ever create copies of them, and therefore can't pass or return by-value.

If you define your own class and don't hide its assignment operator/copy constructor then you can make copies wherever you like.

Something to consider when writing your operators is "Does the operator semantically modify its current object?" Anything involving assignment obviously would be expected to modify the current object, therefore returning a copy would just be weird and probably wrong.

Having an operator return by-value or by-reference really should depend on the operator - it should reflect the way that the operator is expected to work, because if you do it differently, then your operator may behave in an unusual fashion relative to its behaviour for other types such as int or std::string.

So, the following operators are expected to return by-value because they shouldn't change their current object (typically you'd make these into const members)
1
2
3
4
+
-
/
* 


Whereas these operators would all be expected to modify their current object, therefore they should return by-reference
1
2
3
4
5
=
+=
-=
/=
*= 

- Not an exhaustive list, just to give you the general idea.
Last edited on
Thank you. I now see why I need to return by reference for istream case. But for '=' operator, I don't seem to get it yet. The code below worked fine with just 'return by value'. It is true that we are modifying the owner object, but I guess there is no need for it to be returned by ref for the next set of = to work. Like in the below example:

After t3 = t1 has been evaluated, a copy of t3 is returned which is then assigned to t4. Please let me know if I am getting this right.


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
#include <iostream>

using namespace std;

class testclass
{
	int val;
	
	public:
	
	testclass(): val(0) {}
	
	testclass(int v): val(v) {}
	
	testclass(testclass& t)
	{
	  val = t.val;
	}
	
	testclass operator=(const testclass& t)
	{
	  val = t.val;
	  return *this;
	}
	
	int getval() { return val;}
	friend ostream& operator<< (ostream &out, testclass & a);

};

ostream& operator<< (ostream &out, testclass & a) //friend function
{
    cout << a.val;
    return out;
}

int main()
{
  testclass t1(2);
  testclass t2 = t1;
  testclass t3,t4;
  t4 = t3 = t1;
  cout<<"The value in objs is: "<<t1<<t2<<t3<<t4<<endl;
  
  return 0;

}
In the example you've shown, the main difference is simply that you get extra temporary copies of your objects which are immediately thrown away after assignment has finished. Depending on the object, it might not be the end of the world, but it's a bit inefficient and unnecessary (efficency-wise, it would be much worse for really big objects).


The difference would be more visible if you tried to do something with that temporary object immediately afterwards. e.g.
1
2
3
4
5
6
int main()
{
    testclass t1(1);
    testclass t2(2);
    const testclass& t3 = t2 = t1;
} 
If you replaced testclass with std::string, then the above code would be fine, but where testclass::operator= returns a copy, it leaves t3 being a "dangling" reference.
To read the code, you might expect that "t3" would be a reference-to t2, but that wouldn't be the case, since t2 = t1 would have returned a temporary copy, which would be destroyed as soon as the assignment operation finished.


Granted, code like this is quite rare, and not a particularly good idea (It would almost certainly fail a code review out in the real world! :) ) - but for other types such as std::string and int it would at least be "safe".

Last edited on
Topic archived. No new replies allowed.