代码之家  ›  专栏  ›  技术社区  ›  Xirema

如何正确编写运算符的R值重载

  •  10
  • Xirema  · 技术社区  · 7 年前

    在上下文中,我使用的实际类比我在这里展示的要复杂得多,也要大得多,但我只是将此作为一个示例。

    struct Vector {
        int x, y;
        Vector() : Vector(0,0) {}
        Vector(int x, int y) : x(x), y(y) {}
    };
    

    我想添加运算符重载以允许 Vector s将相互相加和相减。

    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!
    }
    

    因此,我重写了代码,以注意对象是L值还是R值:

    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?
    }
    
    3 回复  |  直到 7 年前
        1
  •  6
  •   Yakk - Adam Nevraumont    7 年前

    以下是使这些操作员过载的相对标准的方法:

    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 直接省略到的lhs参数中 +c . 你的第一行 + 包括创建一个副本,所以签名中的额外副本是无害的。

    除非你有很好的理由 += = 在右值上。 int 不支持,你也不应该支持。

        2
  •  2
  •   T.C. Yksisarvinen    7 年前

    当有疑问时,按int do做。

    你能在上做复合赋值吗 int R值?当然不是。那么,为什么要为你的 Vector ?


    你的 b 是安全的,但是 Vector&& c = Vector(1,2) += Vector(5,4); 不是。通常的解决方法是按值返回,但从赋值运算符按值返回也有点奇怪。

        3
  •  0
  •   dimm    7 年前

    移动本身是个坏主意。

    Vector&& operator-=(Vector const& v) && {
        return std::move(*this -= v);
    }
    

    您可以很容易地得到一个moved from对象,而无需这样做。


    总的来说:

    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)
    

    至于在操作符重载中正确使用r值引用,您只能在+,而不能在+=。请参见std::字符串 operator+ operator +=

    推荐文章