9

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

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 回答 8

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

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

  3. 并不真地; 这种方法比最初看起来要有效得多。

于 2010-05-19T13:54:16.427 回答
5

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

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

Dave Abrahams 不久前写了一篇文章,解释了它是如何工作的,以及为什么这种代码通常非常高效,尽管最初看起来它不应该如此。

编辑(谢谢 MSalters):是的,这确实假设/取决于为MyClass. 如果a+b != b+a,那么原始代码就是您想要的(大部分相同的推理适用)。

于 2010-05-19T13:59:48.237 回答
3

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

  • MyClass result = *this不使用赋值运算符,应该是调用拷贝构造函数,就好像写的一样MyClass result(*this)
  • 使用时的返回值a = b + c称为临时值,编译器负责删除它(这可能会发生在语句的末尾,即分号,在完成其他所有操作之后)。您不必担心,编译器将始终清理临时文件。
  • 没有更好的方法,你需要副本。但是,允许编译器优化掉临时副本,因此不会像您想象的那么多。不过,在 C++0x 中,您可以使用移动构造函数通过转移临时内容的所有权而不是整体复制来提高性能。
于 2010-05-19T13:56:08.713 回答
3

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

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

然后是返回中的复制构造函数(它也复制类

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

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

编译器,与任何其他临时值一样。它们在完整表达式的末尾被删除(在这种情况下,它意味着在行;尾或之后。

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

并不真地。这不是那么低效。

于 2010-05-19T13:56:55.547 回答
2

我会尽力回答:

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

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

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

PS我有时更喜欢以operator+非成员的身份实现,以利用运算符双方的隐式转换(仅在有意义的情况下)。

于 2010-05-19T14:01:52.743 回答
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;
}
于 2010-05-19T14:48:47.420 回答
1

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

于 2010-05-19T13:54:36.670 回答
1
  1. 这是在 C++ 中实现 operator+ 的正确方法。您非常害怕的大多数副本都会被编译器忽略,并且会受到 C++0x 中的移动语义的影响。

  2. 该类是临时的,将被删除。如果你将临时绑定到一个临时const&的生命周期将延长到常量引用的生命周期。

  3. 可能将其实现为自由功能更明显一些。MyClass::operator+ 中的第一个参数是隐式 this ,编译器无论如何都会将函数重写为 operator+(const MyClass&, const MyClass&) 。

于 2010-05-19T13:55:09.013 回答