For context, the actual class I'm working with is considerably more complicated and larger than what I'm showing here, but I'm using this just as an example.
struct Vector {
int x, y;
Vector() : Vector(0,0) {}
Vector(int x, int y) : x(x), y(y) {}
};
I'd like to add operator overloads to allow Vector
s to be added and subtracted from each other.
Vector& operator+=(Vector const& v) {
x += v.x;
y += v.y;
return *this;
}
Vector operator+(Vector const& v) const {
return Vector(*this) += v;
}
Vector& operator-=(Vector const& v) {
x -= v.x;
y -= v.y;
return *this;
}
Vector operator-(Vector const& v) const {
return Vector(*this) -= v;
}
However, This code can allow unfortunate constructions:
int main() {
Vector & a = Vector(1,2) += Vector(5,4);//This compiles and invokes undefined behavior!
std::cout << a.x << ',' << a.y << std::endl;//This isn't safe!
}
So I rewrote the code to be mindful of whether the object is an L-value or R-value:
Vector& operator+=(Vector const& v) & {
x += v.x;
y += v.y;
return *this;
}
Vector&& operator+=(Vector const& v) && {
return std::move(*this += v);
}
Vector operator+(Vector const& v) const {
return Vector(*this) += v;
}
Vector& operator-=(Vector const& v) & {
x -= v.x;
y -= v.y;
return *this;
}
Vector&& operator-=(Vector const& v) && {
return std::move(*this -= v);
}
Vector operator-(Vector const& v) const {
return Vector(*this) -= v;
}
So my remaining question is, even though this code compiles and does what I'm expecting, is this code safe and free of unexpected Undefined Behavior?
int main() {
//No Longer compiles, good.
//Vector & a = Vector(1,2) += Vector(5,4);
//Is this safe?
Vector b = Vector(1,2) += Vector(5,4);
//Other cases where this code could be unsafe?
}
An overloaded operator (except for the function call operator) cannot have default arguments or an ellipsis in the argument list. You must declare the overloaded = , [] , () , and -> operators as nonstatic member functions to ensure that they receive lvalues as their first operands.
You can overload the assignment operator (=) just as you can other operators and it can be used to create an object just like the copy constructor.
Explanation: Both arithmetic and non-arithmetic operators can be overloaded. The precedence and associativity of operators remains the same after and before operator overloading.
C++ allows you to specify more than one definition for a function name or an operator in the same scope, which is called function overloading and operator overloading respectively.
Here are relatively standard ways to overload these operators:
Vector& operator+=(Vector const& v)& {
x += v.x;
y += v.y;
return *this;
}
friend Vector operator+(Vector lhs, Vector const& v) {
lhs+=v;
return std::move(lhs); // move is redundant yet harmless in this case
}
Vector& operator-=(Vector const& v)& {
x -= v.x;
y -= v.y;
return *this;
}
friend Vector operator-(Vector lhs, Vector const& v) {
lhs -= v;
return std::move(lhs); // move is redundant yet harmless in this case
}
note that on a line of many +
or -
, the above generates way fewer copies and more moves than your overloads do.
a+b+c
becomes (a+b)+c
, and the return value of a+b
is elided directly into the lhs argument of +c
. And the first line of your +
consisted of creating a copy anyhow, so the extra copy in the signature is harmless.
Unless you have a great reason why, ban +=
and =
on rvalues. int
doesn't support it, you shouldn't either.
When in doubt, do as int do.
Can you do compound assignment on int
rvalues? Of course not. So why bother with your Vector
?
Your b
is safe, but Vector&& c = Vector(1,2) += Vector(5,4);
isn't. The usual fix is to return by value, but returning by value from an assignment operator is also somewhat weird.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With