28

I am a bit confused about the differences between

Type  operator +  (const Type &type);
Type &operator += (const Type &type);

and

friend Type  operator +  (const Type &type1, const Type &type2);
friend Type &operator += (const Type &type1, const Type &type2);

which way is preferred, what do they look like and when should either be used?

Jon
  • 396,160
  • 71
  • 697
  • 768
Dave
  • 6,313
  • 11
  • 49
  • 95
  • 2
    Note that the two examples you present are _not_ the same. In the first examples, the left operand cannot be const-qualified; in the second examples it can. To allow the first examples to take a const-qualified left operand, you need to qualify the member function, e.g. `T operator+(const T& t) const;`. – James McNellis Jan 11 '11 at 00:28
  • 1
    More or less a duplicate: http://stackoverflow.com/questions/4622330/operator-overloading-member-function-vs-non-member-function/4622446 – Charles Salvia Jan 11 '11 at 01:09
  • 3
    The `friend` keyword has no direct relation to the issue and can only add to the confusion. There's absolutely no requirement for a standalone operator to be declared as a `friend`. Even though you one can often hear this approach referred to as "declaring operator as a friend", the reference to friendship in this context is a confusing misnomer. – AnT Jan 11 '11 at 01:38
  • 2
    @AndreyT: although occasionally you do see a non-member operator overload as `friend` not because of anything to do with access control, but simply because that's the way to put the definition of a non-member function inside a class definition. So in that situation the two issues are related. – Steve Jessop Jan 11 '11 at 02:32
  • 2
    @James: to be specific, in both cases `operator+` should take `const` for its first operand, and `operator+=` definitely shouldn't. – Steve Jessop Jan 11 '11 at 02:33
  • Do you need to define both, or is C++ smart enough to make `+=` work if you only define `+` ? – theonlygusti Mar 19 '21 at 22:29

3 Answers3

30

The first form of the operators is what you would define inside class Type.

The second form of the operators is what you would define as free-standing functions in the same namespace as class Type.

It's a very good idea to define free-standing functions because then the operands to those can take part in implicit conversions.

Example

Assume this class:

class Type {
    public:
    Type(int foo) { }

    // Added the const qualifier as an update: see end of answer
    Type operator + (const Type& type) const { return *this; }
};

You could then write:

Type a = Type(1) + Type(2); // OK
Type b = Type(1) + 2; // Also OK: conversion of int(2) to Type

But you could NOT write:

Type c = 1 + Type(2); // DOES NOT COMPILE

Having operator+ as a free function allows the last case as well.

What the second form of the operator does wrong though is that it performs the addition by directly tweaking the private members of its operands (I 'm assuming that, otherwise it would not need to be a friend). It should not be doing that: instead, the operators should also be defined inside the class and the free-standing functions should call them.

To see how that would turn out, let's ask for the services of a guru: http://www.gotw.ca/gotw/004.htm. Scroll at the very end to see how to implement the free-standing functions.

Update:

As James McNellis calls out in his comment, the two forms given also have another difference: the left-hand-side is not const-qualified in the first version. Since the operands of operator+ should really not be modified as part of the addition, it's a very very good idea to const-qualify them all the time. The class Type in my example now does this, where initially it did not.

Conclusion

The best way to deal with operators + and += is:

  1. Define operator+= as T& T::operator+=(const T&); inside your class. This is where the addition would be implemented.
  2. Define operator+ as T T::operator+(const T&) const; inside your class. This operator would be implemented in terms of the previous one.
  3. Provide a free function T operator+(const T&, const T&); outside the class, but inside the same namespace. This function would call the member operator+ to do the work.

You can omit step 2 and have the free function call T::operator+= directly, but as a matter of personal preference I 'd want to keep all of the addition logic inside the class.

Jon
  • 396,160
  • 71
  • 697
  • 768
  • The operator isn't necessarily "tweaking" anything; sometimes there's just data that you don't want to provide an accessor for, but that matters to the operation. – cHao Jan 11 '11 at 00:14
  • I usually define the modifying operation (i.e. +=) inside the class, then define operator + as a free function in terms of +=. Create a copy of arg1, then return tmp += arg2. This lets you define the addition in one place. This method is detailed in one of Meyer's Effective C++ books. – Daniel Gallagher Jan 11 '11 at 00:31
  • @cHao: Since you don't want to provide access to the data, force all interested parties to call through your own method (this would be the first form of `operator+`). This is what I 'm saying. – Jon Jan 11 '11 at 00:32
  • @Daniel Gallagher: This is the way to go, I fully agree. If you take a look at the Herb Sutter link, that's how it's done there as well. – Jon Jan 11 '11 at 00:33
  • @Jon: You said it yourself: with the free-standing functions, *the operands can take part in implicit conversions*. Sometimes you want stuff like that, but without necessarily making the private data part of your public API. – cHao Jan 11 '11 at 00:39
  • @cHao: I 'm not saying "do only the member operator". I 'm saying "do *both* operators, but don't let the free one be a `friend`. Just have it call the member". I think we don't disagree in anything. – Jon Jan 11 '11 at 00:46
  • @Jon: as Daniel pointed out, it's typical to define `+` in terms of `+=`. Your answer recommends doing it the other way around. (The GOTW you link agrees with Daniel.) – Tony Delroy Jan 11 '11 at 01:13
  • @Tony: sorry, I messed that up a bit (mindblock). Fixed it now. – Jon Jan 11 '11 at 01:22
  • 2
    @Jon: the best way: `T& T::operator+=(T const&);` and `T operator+(T lhs, T const& rhs) { return lhs += rhs; }` --> your step 2. is spurious, your signature is off in step 3. (compiler can optimize the copy in function signature better, especially with the upcoming move semantics) – Matthieu M. Jan 11 '11 at 07:29
  • @Matthieu: Added comment re step 2, it's just preference. But why is losing the `const&` on `lhs` better? – Jon Jan 11 '11 at 13:38
  • @Jon: semantically, it makes no difference. From a compiler optimization point of view however, it allows to elide temporaries. If you chain your operators, like so `Type d = a + b + c` and use `T operator+(T const& lhs, T const& rhs);`, what really happens is `Type t = a + b` followed by `Type d = t + c`. You have created a temporary (`t`). – Matthieu M. Jan 11 '11 at 13:44
  • @Jon: if you use the form `T operator+(T lhs, T const& rhs);` however, then the semantics are: copy `a`, in `a + b` modify `lhs` and return a copy as `t`, copy `t`, in `t + c` modify `lhs`, return a copy as `d`. However ALL those copies may be elided thanks to RVO (return value optimization). So if your compiler is reasonably smart, it'll do: copy `a`, add `b` to it, copy `t` (in `d`), add `c` to it. Which is no worse. But if it really is smart, it'll do: copy `a` (in `d`), add `b` to it, add `c` to it. The form is better because you cannot (really) lose, but can win some copy elisions :) – Matthieu M. Jan 11 '11 at 13:49
  • @Matthieu: But that signature would not play well with my `T::operator+`'s `const` status, correct? So if I want to go in for helping the compiler optimize, would there be a price to pay in "correctness"? – Jon Jan 11 '11 at 14:03
  • @Jon: no, the signature plays well with constness, after all the copy constructor takes a const reference to the object to be copied. – Matthieu M. Jan 11 '11 at 14:42
  • @Jon: I really don't understand the purpose of duplicating the code in step two. You only need the mutating version, and the free-function non-mutating version. What purpose is there for this extra function? – GManNickG Jan 11 '11 at 22:42
6

The proper way to implement operators, with respect to C++03 and C++0x (NRVO and move-semantics), is:

struct foo
{
    // mutates left-operand => member-function
    foo& operator+=(const foo& other)
    {
        x += other.x;

        return *this;
    }

    int x;
};

// non-mutating => non-member function
foo operator+(foo first, // parameter as value, move-construct (or elide)
                const foo& second) 
{
    first += second; // implement in terms of mutating operator

    return first; // NRVO (or move-construct)
}

Note it's tempting to combine the above into:

foo operator+(foo first, const foo& second) 
{
    return first += second;
}

But sometimes (in my testing) the compiler doesn't enable NRVO (or move semantics) because it can't be certain (until it inlines the mutating operator) that first += second is the same as first. Simpler and safer is to split it up.

bobobobo
  • 57,855
  • 58
  • 238
  • 337
GManNickG
  • 459,504
  • 50
  • 465
  • 534
  • 1
    +1 for a good answer, but recommending something based on the quirks of your compiler's optimiser (without even saying which compiler(s)) is a bit short-sighted ;-P – Tony Delroy Jan 11 '11 at 01:16
  • @GMan: FYI, it's not *very* tempting. – Steve Jessop Jan 11 '11 at 02:39
  • @Steve: I do consider it tempting :) @GMan: Would you happen to know (without an inlined `+=` and `+`) what `a = a + b` would give ? I don't understand how NRVO could kick in here, since if `+=` were to throw, then `a` is supposedly unmodified. – Matthieu M. Jan 11 '11 at 07:34
  • @Tony: Sorry, was MSVC. @Matt: Hm, good point. My tests were actually with move-constructing and I just assumed (I think now incorrectly) NRVO would follow the same criteria. – GManNickG Jan 11 '11 at 07:41
  • 1
    @Matthieu: NRVO is a *copy constructor* elision, not a *copy assignment operator* elision. AFAIK, the caller can't provide an existing object as the target for the return value, it has to be an object being constructed. In cases where the caller can't make use of NRVO, it just provides a temporary and assigns from that, doesn't it? – Steve Jessop Jan 11 '11 at 13:27
  • @Steve: Ah! Good point, in this case it would then be up to the caller to provide for a temporary (NRVO thus still applies to initialize the temporary) and then use the move constructor (if the language allows) to change the state of `a` (from my previous example). Thanks Steve :) – Matthieu M. Jan 11 '11 at 13:39
  • That is a nice idea to implement `operator+` in terms of `operator+=`. It reduces the amount of repeated code. I usually wrote `operator+` as `foo operator+(const foo& first, const foo& second) { foo temp ; temp = ( first + second implementation ) ; return temp ; }` – bobobobo Dec 08 '13 at 17:26
0

The friend specifier is used when the thing being declared isn't a member of the class, but needs access to the private members of the class's instances in order to do its job.

If your operator will be defined in the class itself, use the first way; if it'll be a standalone function, use the second.

cHao
  • 78,897
  • 19
  • 136
  • 168