Operator Overloading
Member or non-member, and why that choice determines whether 2 + a compiles.
Intermediate
+ means for two built-in ints. And there's
no automatic relationship between related operators: defining operator+ doesn't
give you operator+= for free, and vice versa. If you want both, and want them to
behave consistently, you write both — the idiomatic way is to implement
operator+= first, then define operator+ in terms of it.
| Member function | Non-member function | |
|---|---|---|
| Left operand | implicitly *this | an explicit first parameter |
| Arguments (binary op) | one (the right operand) | two (both operands) |
| Implicit conversion of left operand | doesn't happen — left operand must already be the class type | works — the compiler can convert a built-in left operand via a non-explicit constructor |
| Access | full access to private members | needs friend, or must go through the public interface |
+= as members, but binary operators like
+ as non-members) holds up in practice, not just as a style rule:
class Complex {
double re, im;
public:
Complex(double re = 0, double im = 0) : re(re), im(im) {}
Complex & operator+=(const Complex &rhs) { re += rhs.re; im += rhs.im; return *this; }
Complex operator+(const Complex &rhs) const { return Complex(*this) += rhs; } // member version
};
Complex a(1, 2), b(3, 4);
a + b; // fine either way — a.operator+(b)
a + 2; // fine — 2 implicitly becomes Complex(2, 0), then a.operator+(Complex(2,0))
2 + a; // ERROR with the member version — the compiler would need 2.operator+(a), and int has no members
Complex operator+(const Complex &lhs, const Complex &rhs) { return Complex(lhs) += rhs; } // non-member
2 + a; // now fine — operator+(Complex(2,0), a), found via argument-dependent lookup
int→Complex conversion (a
temporary Complex gets constructed just to be added and discarded), overload
for the mixed-type cases directly instead of relying on the conversion:
Complex operator+(const Complex &lhs, const Complex &rhs) { return Complex(lhs) += rhs; }
Complex operator+(const Complex &lhs, double rhs) { return Complex(lhs) += rhs; }
Complex operator+(double lhs, const Complex &rhs) { return Complex(rhs) += lhs; }
friend if they're implemented in terms of a
public += — reserve friend for the rarer case where an
operator genuinely needs to reach into private state that no public member exposes.
class Complex {
double re, im;
public:
bool operator==(const Complex &rhs) const { return re == rhs.re && im == rhs.im; }
bool operator!=(const Complex &rhs) const { return !(*this == rhs); } // defined in terms of ==
};
!= as !(*this == rhs) is the same idea as
+ in terms of +=: implement the comparison once, and get the
inverse for free without a second, independently-maintained implementation that could drift
out of sync.
int
parameter on the postfix version that exists purely to give the compiler two different
signatures to choose between — it's never actually passed a value. Prefix returns a
reference to the (already-modified) object; postfix has to save the old value in a local
before modifying, then return that saved copy — which is why postfix is inherently a
little more expensive than prefix, for any type more complex than a built-in int.
class Counter {
int value = 0;
public:
Counter & operator++() { // prefix: ++c
value += 1;
return *this; // returns the updated object itself
}
Counter operator++(int) { // postfix: c++ — the "int" here is just a marker, never used
Counter temp = *this; // save the pre-increment value
value += 1;
return temp; // return the OLD value, by value (a copy)
}
};
Counter c;
++c; // "pre-increment" — modifies c, then the expression's value is the new c
c++; // "post-increment" — the expression's value is the old c, then c is modified
++c over c++ in a plain statement (a for
loop increment, for instance) where the returned value isn't used — it skips the copy
postfix has to make, for free, with identical observable behavior.
e1 + e2, differing only in
where they live and what they can access:
TopNotchNote/cpp/operator_overloading_binary_forms.cpp
class Rational {
int num, den;
public:
Rational(int n, int d) : num(n), den(d) {}
operator double() const { return static_cast<double>(num) / den; } // implicit Rational -> double
};
Rational r(1, 2);
double d = r; // 0.5 — the conversion operator ran implicitly
std::cout << r + 0.25; // 0.75 — r is silently converted to participate in the addition
explicit
(C++11 onward) if you want the conversion to require static_cast<double>(r)
spelled out rather than happening silently.