return value of operator overloading in C++ [duplicate]
Asked Answered
M

5

53

I have a question about the return value of operator overloading in C++. Generally, I found two cases, one is return-by-value, and one is return-by-reference. So what's the underneath rule of that? Especially at the case when you can use the operator continuously, such as cout<<x<<y.

For example, when implementing a + operation "string + (string)". how would you return the return value, by ref or by val.

Margalo answered 25/2, 2010 at 20:5 Comment(0)
A
80

Some operators return by value, some by reference. In general, an operator whose result is a new value (such as +, -, etc) must return the new value by value, and an operator whose result is an existing value, but modified (such as <<, >>, +=, -=, etc), should return a reference to the modified value.

For example, cout is a std::ostream, and inserting data into the stream is a modifying operation, so to implement the << operator to insert into an ostream, the operator is defined like this:

std::ostream& operator<< (std::ostream& lhs, const MyType& rhs)
{
  // Do whatever to put the contents of the rhs object into the lhs stream
  return lhs;
}

This way, when you have a compound statement like cout << x << y, the sub-expression cout << x is evaluated first, and then the expression [result of cout << x ] << y is evaluated. Since the operator << on x returns a reference to cout, the expression [result of cout << x ] << y is equivalent to cout << y, as expected.

Conversely, for "string + string", the result is a new string (both original strings are unchanged), so it must return by value (otherwise you would be returning a reference to a temporary, which is undefined behavior).

Apprehension answered 25/2, 2010 at 20:8 Comment(14)
Which goes hand in hand with "if it's const, return a value, if it's non-const, return a reference".Hamford
Technically << does not modify the existing value. It's just a left-shift operator. It's the stream interface breaking the rules.Kathie
@GMan correct, although that doesn't cover the cases (such as operator<<) where the overload is implemented as a non-member.Apprehension
@KennyTM I had no idea old C codgers were still putting up that fight after so many years. :)Apprehension
@Tyler: Indeed, it's only a general case mostly intended for member functions.Hamford
@Tyler: Haha. Too much C and Python makes me forgot about their use cases for streams. (My first reaction was "do you mean <<=? :p)Kathie
@KennyTM, that exception to the rules just enhances the point of this answer. When applied to an integer, operator<< is a left shift and doesn't modify the original so it should return by value; when applied to a stream it does modify the stream so it should return by reference.Applejack
@MarkRansom: what is the value of a stream?Urge
@nocomprende, a stream is an object so it has a value, but I'm not sure what "return by value" would do to it so I'm going to leave that question unanswered.Applejack
@MarkRansom: I guess I think of a stream as basically connecting to either the console (whatever that might be these days) or a file (or a network I/O endpoint, which is equivalent). Neither one is really any kind of an object, they are more like parts of the computer. But then, I learned to program on a PDP 11/7 and the Apple ][, so objects were not part of programming then. You can't have more than one console, and creating new files is why computers were invented - so they are not objects, to me. If you are outputting to a stream, it doesn't have a value per se. Write-only memory?Urge
@nocomprende the stream object isn't a console or file, it's a C++ construct that connects to a console or file (or other things e.g. stringstream). Its "value" is very much implementation dependent, and is not the same as the state of the resource that it connects to.Applejack
@MarkRansom: Next you'll be saying that sockets were originally designed to do IPC on a non-networked computer. (That IS why they were designed.) (Shhh! You'll ruin the story.)Urge
@TylerMcHenry: should I return by value or return by reference when overloading prefix & postfix operators like ++, -- etc?Middling
@Middling Start by thinking how those operators work for int, whether they modify the object or produce a copy with a new value, and then follow the rules given in this answer. Spoiler: you should by reference for the prefix operators, which return modifiable lvalue references to the original object with its after-(inc|dec)remented value - and by value for the postfix forms, which return copies of the object with its original value (i.e. not the new value that it, itself, now has).Chagrin
A
16

To attempt an answer to your question regarding strings, the operator+() for strings is almost always implemented as a free (non-member) function so that implicit conversions can be performed on either parameter. That is so you can say things like:

string s1 = "bar";
string s2 = "foo" + s1;

Given that, and that we can see that neither parameter can be changed, it must be declared as:

RETURN_TYPE operator +( const string & a, const string & b );

We ignore the RETURN_TYPE for the moment. As we cannot return either parameter (because we can't change them), the implementation must create a new, concatenated value:

RETURN_TYPE operator +( const string & a, const string & b ) {
    string newval = a;
    newval += b;    // a common implementation
    return newval;
}

Now if we make RETURN_TYPE a reference, we will be returning a reference to a local object, which is a well-known no-no as the local object don't exist outside the function. So our only choice is to return a value, i.e. a copy:

string operator +( const string & a, const string & b ) {
    string newval = a;
    newval += b;    // a common implementation
    return newval;
}
Accost answered 25/2, 2010 at 20:29 Comment(2)
So, for freeing the allocated result, am I correct that because the invocation of the Operator is in effect a declaration, the anonymous return value goes on the stack, and so is freed when the containing function returns?Urge
@anon: In this case, it is more concise and possibly better performing (in the case where a is constructed by the call) to simply take a by value and modify/return that, rather than needing any other intermediate string. Generally, if you need to copy an argument, take it by value: that's getting a copy for free. So those 3 lines are simplified to return a += b;. @user: The mistake there was thinking about "freeing" and "allocation" at all. The whole point of RAII and classes like std::string is to avoid the end user having to worry about freeing. Just use the result and let RAII KO itChagrin
D
7

If you want your operator overload to behave like the built-in operator, then the rule is pretty simple; the standard defines exactly how the built-in operators behave and will indicate if the result of a built-in is an rvalue or an lvalue.

The rule you should use is:

  • if the built-in operator returns an rvalue then your overload should return a reference
  • if the built-in returns an lvalue then your overload should return a value

However, your overload isn't required to return the same kind of result as the built-in, though that's what you should do unless you have a good reason to do otherwise.

For example, KennyTM noted in a comment to another answer that the stream overloads for the << and >> operators return a reference to the left operand, which is not how the built-ins work. But the designers of the stream interface did this so stream I/O could be chained.

Dwan answered 25/2, 2010 at 20:26 Comment(2)
For reference the built-in operators are in § 13.6 of the N3337 draft C++11 standard, which is the only version of the standard I have at hand.Applejack
UV for Do as the ints Do!Chagrin
A
4

Depending on the operator you may have to return by value.

When both can be used though, like in operator+= you could consider the following:

  • If your objects are immutable it's probably better to return by value.
  • If your objects are mutable it's probably better to return by reference.
Ampersand answered 25/2, 2010 at 20:8 Comment(1)
The purpose of operator+= is to mutate the object. It should not be called on immutable objects.Degeneration
R
3

Usually you return by reference in an operation that changes the value of the things it's operating on, like = or +=. All other operations are return by value.

This is more a rule of thumb, though. You can design your operator either way.

Recusant answered 25/2, 2010 at 20:9 Comment(1)
It's a rule of thumb because in most cases, a user will expect - & quite possibly need - overloaded operators in user-defined classes to have the same semantics as the counterparts for built-in types. "Do as the ints do" is generally the best advice. Obviously, this isn't always 100% feasible (although my mind has just gone blank of examples), or sometimes you're doing something wild like ostream.operator<< or path.operator/... but in most cases, the int-style behaviour is both possible & desirable. You "can" do "either way" - but chances are you'll just annoy users if you flip them!Chagrin

© 2022 - 2024 — McMap. All rights reserved.