如何为运算符正确编写 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)+ca+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 +=