谁在+运算符中删除了复制的实例? (C ++)

时间:2010-05-19 13:48:06

标签: c++ memory copy variable-assignment operator-keyword

我搜索了如何在整个互联网上正确实施+运营商,我发现的所有结果都执行以下步骤:

const MyClass MyClass::operator+(const MyClass &other) const
{
    MyClass result = *this;  // Make a copy of myself. Same as MyClass result(*this);
    result += other;         // Use += to add other to the copy.
    return result;           // All done!
}

关于这个“过程”我几乎没有问题:

  1. 用这种方式实现+运算符不是很愚蠢,它在第一行调用赋值运算符(复制类),然后在返回中调用复制构造函数(也复制类,因为回报是按价值计算的,所以它会破坏第一份副本并创建一个新版本......坦白说这并不是很聪明......)

  2. 当我写a = b + c时,b + c部分创建该类的新副本,然后'a ='部分将副本复制给自己。 谁删除了b + c创建的副本?

  3. 有没有更好的方法来实现+运算符而不需要两次处理类,也没有任何内存问题?

  4. 提前致谢

8 个答案:

答案 0 :(得分:6)

  1. 这实际上不是赋值运算符,而是复制构造函数。毕竟像添加这样的操作会创建一个新值,因此必须在某处创建它。这比看起来更有效,因为编译器可以自由地进行返回值优化,这意味着它可以直接在下一次使用它的位置构造值。

  2. result被声明为局部变量,因此在函数调用中消失 - 除非使用了RVO(见上文),在这种情况下它从未在函数中实际创建,但是在来电者中。

  3. 不是真的;这种方法比起初看起来效率更高。

答案 1 :(得分:5)

在这种情况下,我可能会考虑这样的事情:

MyClass MyClass::operator+(MyClass other) { 
     other += *this;
     return other;
}

Dave Abrahams写了article一段时间回来解释这是如何工作的,以及为什么这种代码通常非常有效,即使它最初似乎不应该是。

编辑(谢谢MSalters):是的,这确实假设/依赖于MyClass的交换属性。如果是a+b != b+a,则原始代码就是您想要的(大多数相同的推理都适用)。

答案 2 :(得分:3)

这似乎是实施operator+的正确方法。几点:

  • MyClass result = *this不使用赋值运算符,它应该调用复制构造函数,就好像它是MyClass result(*this)一样。
  • a = b + c中使用时返回的值称为临时,编译器负责删除它(这可能发生在语句的末尾,即分号,其他一切都完成之后)。您不必担心,编译器将始终清理临时工。
  • 没有更好的方法,你需要副本。但是,编译器可以优化临时副本,因此不会像您想象的那样多。但是,在C ++ 0x中,您可以使用移动构造函数来转移临时内容的所有权,而不是将其复制到其中,从而提高性能。

答案 3 :(得分:3)

  

它在第一行调用赋值运算符(复制类)

不,这是复制初始化(通过构造函数)。

  

然后返回复制构造函数(也复制类

编译器可以(并且通常会)使用NRVO来删除此副本。

  

当我写a = b + c时,b + c部分创建该类的新副本,然后'a ='部分将副本复制给自己。谁删除了b + c创建的副本

编译器,与任何其他临时值一样。它们在完整表达结束时被删除(在这种情况下,它表示在行尾;处或之后。

  

有没有更好的方法来实现+运算符而不需要两次处理类,也没有任何内存问题?

不是真的。这不是那么低效。

答案 4 :(得分:2)

我会尽力回答:

Point(1):不,它不会调用赋值运算符。相反,它调用一个构造函数。由于您无论如何都需要构造对象(因为operator+返回一个副本),这不会引入额外的操作。

Point(2):临时result是在堆栈中创建的,因此不会引入内存问题(当函数退出时会被销毁)。在return上创建临时值,以便即使在a被销毁后,也可以使用赋值(或复制构造函数)将结果分配给a=b+c;result) 。该临时文件由编译器自动销毁。

第(3)点:以上是标准规定的内容。请记住,只要效果与标准规定的相同,就允许编译器实现者优化实现。我相信,编译器在现实中优化了许多在这里发生的复制。使用上面的习语是可读的,实际上效率不高。

P.S。我有时更喜欢将operator+实现为非成员,以便为运营商的双方利用隐式转换(只有在有意义的情况下)。

答案 5 :(得分:1)

没有内存问题(假设赋值运算符和复制构造函数写得很好)。只是因为这些对象的所有内存都在堆栈中并由编译器管理。此外,编译器会对最终a进行优化并直接执行所有操作,而不是复制两次。

答案 6 :(得分:1)

  1. 这是在C ++中实现operator +的正确方法。您非常害怕的大多数副本都会被编译器剔除,并且会受到C ++ 0x中移动语义的影响。

  2. 该课程是临时课程,将被删除。如果将临时值绑定到const&,则临时的生命周期将延长到const引用的生命周期。

  3. 将其作为免费功能实现可能会更加明显。 MyClass :: operator +中的第一个参数是隐式的,编译器会将函数重写为operator +(const MyClass&,const MyClass&)。

答案 7 :(得分:1)

据我记忆,Stroustrup的'C ++编程语言'建议仅在内部表示受操作影响时才将操作符实现为成员函数,而不是当外部函数影响外部函数时。如果基于operator + =实现,operator +不需要访问内部表示。

所以你会:

class MyClass
{
public:
  MyClass& operator+=(const MyClass &other)
  {
    // Implementation
    return *this;
  }
};

MyClass operator+(const MyClass &op1, const MyClass &op2)
{
    MyClass r = op1;
    return r += op2;
}