如何为运算符正确编写 R 值重载
How to properly write R-Value overloads for operators
对于上下文,我实际使用的 class 比我在这里展示的要复杂得多,也更大,但我只是用它作为例子。
struct Vector {
int x, y;
Vector() : Vector(0,0) {}
Vector(int x, int y) : x(x), y(y) {}
};
我想添加运算符重载以允许 Vector
彼此相加和相减。
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;
}
但是,此代码可能允许不幸的构造:
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!
}
所以我重写了代码以注意对象是左值还是右值:
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;
}
所以我剩下的问题是,即使这段代码可以编译并执行我预期的,这段代码是否安全并且没有意外的未定义行为?
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?
}
如有疑问,照常做。
你能对 int
右值进行复合赋值吗?当然不是。那么为什么要为你的 Vector
烦恼呢?
您的 b
是安全的,但 Vector&& c = Vector(1,2) += Vector(5,4);
不是。通常的解决方法是按值 return,但是从赋值运算符按值 return 也有些奇怪。
以下是重载这些运算符的相对标准的方法:
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
}
请注意,在许多 +
或 -
的一行中,与您的重载相比,上面生成的副本更少,移动更多。
a+b+c
变为(a+b)+c
,a+b
的return值被直接省略到+c
的lhs参数中。而且你的 +
的第一行无论如何都要创建一个副本,所以签名中的额外副本是无害的。
除非你有充分的理由,否则请禁止使用 +=
和 =
右值。 int
不支持,你也不应该支持。
移动本身是个坏主意。
Vector&& operator-=(Vector const& v) && {
return std::move(*this -= v);
}
您很容易以出乎意料的方式结束移动对象。
主要是:
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!
}
为什么不直接使用
Vector a = Vector(1,2) + Vector(5,4)
至于在运算符重载中正确使用右值引用,您只能在 + 中使用它们,而不能在 += 中使用。见 std::string operator+ and operator +=
对于上下文,我实际使用的 class 比我在这里展示的要复杂得多,也更大,但我只是用它作为例子。
struct Vector {
int x, y;
Vector() : Vector(0,0) {}
Vector(int x, int y) : x(x), y(y) {}
};
我想添加运算符重载以允许 Vector
彼此相加和相减。
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;
}
但是,此代码可能允许不幸的构造:
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!
}
所以我重写了代码以注意对象是左值还是右值:
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;
}
所以我剩下的问题是,即使这段代码可以编译并执行我预期的,这段代码是否安全并且没有意外的未定义行为?
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?
}
如有疑问,照常做。
你能对 int
右值进行复合赋值吗?当然不是。那么为什么要为你的 Vector
烦恼呢?
您的 b
是安全的,但 Vector&& c = Vector(1,2) += Vector(5,4);
不是。通常的解决方法是按值 return,但是从赋值运算符按值 return 也有些奇怪。
以下是重载这些运算符的相对标准的方法:
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
}
请注意,在许多 +
或 -
的一行中,与您的重载相比,上面生成的副本更少,移动更多。
a+b+c
变为(a+b)+c
,a+b
的return值被直接省略到+c
的lhs参数中。而且你的 +
的第一行无论如何都要创建一个副本,所以签名中的额外副本是无害的。
除非你有充分的理由,否则请禁止使用 +=
和 =
右值。 int
不支持,你也不应该支持。
移动本身是个坏主意。
Vector&& operator-=(Vector const& v) && {
return std::move(*this -= v);
}
您很容易以出乎意料的方式结束移动对象。
主要是:
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!
}
为什么不直接使用
Vector a = Vector(1,2) + Vector(5,4)
至于在运算符重载中正确使用右值引用,您只能在 + 中使用它们,而不能在 += 中使用。见 std::string operator+ and operator +=