【问题标题】:Correct way of defining the addition operator in C++在 C++ 中定义加法运算符的正确方法
【发布时间】:2014-08-25 12:00:39
【问题描述】:

我想知道在 C++ 中编写 operator + 的正确方法是什么(假设我正在为类 c 定义的对象定义加法运算符)?

c c::operator+(const c rhs) { /* do addition */ }

c c::operator+(const c& rhs) { /* do addition */ }

【问题讨论】:

  • 通常,作为非成员,采用两个 const 引用(有些人可能认为最好按值采用一个参数,我倾向于不同意这一点。)
  • const c& 会更高效,因为您通过引用传递,因此不会复制
  • @juanchopanza 谢谢。但是如果我想把它们写成成员(就像上面一样)呢?
  • 你不应该这样。您可能希望将operator+= 实现为成员,然后在非成员operator+ 中使用它
  • @PBM:可能会带来一些便利,但会以丢失复制省略机会为代价。如果你想通过引用来获取参数,你应该编写两个重载,一个用于 const-lvalue,一个用于 rvalue 引用。

标签: c++ operators


【解决方案1】:

让我们举一个具体的例子:

class Int
{
    int n_;

public:
    // constructors...

    Int & operator+=(Int const & rhs) { n_ += rhs.n_; return *this; }
    Int & operator+=(Int && rhs)      { n_ += rhs.n_; return *this; }

我们已经阐明了复合加法的两个重载,它们恰好是相同的,但我们可以想象,右值引用重载通常会提高效率。

现在如何写operator+?一种选择是按值取 RHS:

    Int operator+(Int rhs) { return Int(*this) += std::move(rhs); }

这个版本当然很容易编写和工作。这很方便,因为我们只需要一个重载来覆盖左值和右值参数。但请注意,此代码需要进行复制:构造 Int(*this) 不可复制。因此,如果您绝对不想给用户带来任何不必要的成本,您可能希望加倍努力并重新实现非复合运算符:

    Int operator+(Int const & rhs)
    { Int result(*this); result += rhs; return result; }

    Int operator+(Int && rhs)
    { Int result(*this); result += std::move(rhs); return result; }

还有第三种选择:如果你的操作是可交换的,你可以返回按值参数:

    Int operator+(Int rhs) &  { return rhs += *this; }
    Int operator+(Int rhs) && { return rhs += std::move(*this); }

【讨论】:

  • 活生生的例子:Optimized, Lazy
  • 是否有证据表明右值引用重载可以提高效率?
  • @AntonSavin:这只是一个例子。考虑类似链表或绳子类。
  • 请注意,链接的示例代码 auto x = Int(10) + Int(20) + Int(30); 仍会产生至少 五个 结构。更优化的代码可以使用三个:Int x(10); x += Int(20); x += Int(30);
  • 我很确定第三个版本会抑制 RVO。
【解决方案2】:

定义操作+(以及其他类似的操作,如-等)的推荐方法是定义+=运算符,然后根据+=定义+。操作符+= 应该是类的公共成员,应该通过 const 引用接受一个参数,并且应该返回对 this 的引用:

c& operator+=(const c& other)
{
  ... // modify the object in appropriate way, e.g. if it has a member x do
  ... // x += other.x;
  return *this;
}

那么很容易定义operator+。应该是类外定义的全局函数:

c operator+(const c& lhs, const c& rhs)
{
  c that = lhs;
  that += rhs;
  return that;
}

当然,可以用其他方式定义这些运算符,而且通常其他方式可能更好,但应该有一些理由这样做。否则最好坚持使用这个默认值。

【讨论】:

    【解决方案3】:

    通常,您应该更喜欢 const 引用版本,因为您已经保证不会更改引用的对象并且它避免调用复制构造函数。除此之外,应该没有太大区别,除非你在 operator+() 中做了一些不安全的事情,在这两种情况下都会中断(比如获取传递的对象或其成员的地址,并对对象的生命周期做出不满意的假设或有缺陷的副本构造函数实现)。

    根据您的类c 的语义,复制构造函数也可能有一些不希望的副作用,但这只有您作为c 类的作者才能知道。

    此外,根据您在 operator+() 中所做的操作以及编译器可以直接看到哪些类 c 的代码,编译器有可能会优化按值传递的情况,以便您的结果机器码是一样的。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2015-09-25
      • 1970-01-01
      • 1970-01-01
      • 2020-05-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多