5
#include<iostream>
using namespace std;
class X
{
    int i;

    public:
    X(int a=0) : i(a) {}

    friend X operator+ (const X& left,const X&right);  

};
X operator+ (const X& left,const X&right)  // Method 1
{
    return X(left.i + right.i);
}

X operator+ (const X& left,const X&right) // Method 2
{
    X temp(left.i + right.i);
    return temp;
}

int main()
{
    X a(2),b(3),c;

    c=a+b;

    c.print();
    return 0;
}

在此代码中,运算符 + 通过 2 种不同的方法重载。

我的问题是这些方法之间有什么区别,哪些应该被认为更实用?

4

4 回答 4

6

我看不到任何编译器会在这两个版本之间生成不同代码的情况。第二个稍微冗长一些,但是在这种情况下允许编译器优化掉名义上的额外副本,我不知道有任何编译器不会做这种省略。

也就是说,这是一个微优化:编写最清晰的代码,然后将我带到我的最后一点。不要编写这些运算符中的任何一个,而是将惯用版本与+=:

X& operator+=(const X&right) { i += right.i; return *this; }
X operator+(X left, const X& right) { return left += right; }
于 2013-06-17T19:58:52.820 回答
3

这两种方法之间没有区别,您应该使用最能传达其意图的一种。

关于复制省略的第 12.8/31 段规定:

当满足某些条件时,允许实现省略类对象的复制/移动构造,即使为复制/移动操作选择的构造函数和/或对象的析构函数具有副作用。在这种情况下,实现将省略的复制/移动操作的源和目标简单地视为引用同一对象的两种不同方式,并且该对象的销毁发生在两个对象本应被删除的较晚时间。没有优化就被破坏了。这种复制/移动操作的省略,称为复制省略,在以下情况下是允许的(可以组合起来消除多个副本):

— 在return具有类返回类型的函数的语句中,当表达式是具有与函数返回类型相同的 cv 非限定类型的非易失性自动对象(函数或 catch 子句参数除外)的名称时,通过将自动对象直接构造到函数的返回值中,可以省略复制/移动操作

— [...]

如您所见,创建临时对象和命名具有自动存储持续时间的本地对象的id 表达式都符合复制省略的条件。

此外,为了从函数中返回它,编译器会将本地视为temp(在这种情况下读作:就像一个临时值)。C++11 标准的第 12.8/32 段规定:

当满足或将满足复制操作的省略标准时,除了源对象是函数参数,并且要复制的对象由左值指定之外,选择复制的构造函数的重载决策是首先执行好像对象是由 rvalue 指定的。[...]

因此,我强烈建议从返回类型中删除限定条件const

    const X operator + (const X& left, const X&right)
//  ^^^^^
//  Don't use this!

在 C++11 中,这将禁止移动语义,因为你不能从一个const对象移动,即使它是一个右值 - 简而言之,X如果存在,则不会选择 的移动构造函数,并且复制构造函数将是叫。

于 2013-06-17T19:48:56.863 回答
2

不同之处在于方法一使用了一个叫做“返回值优化”的概念。

方法一:

当编译器发现您对它正在创建的对象没有任何用处时,只能返回它。编译器利用了这一点,“它直接在该值应该返回到的位置构建对象”。在这里,只需要一个普通的构造函数调用(不需要复制构造函数)并且没有析构函数调用,因为您实际上从未创建本地对象。这更有效。


方法二:

第一个名为 temp 的临时对象被创建。然后复制构造函数将 temp 复制到 oustide 返回值的位置。然后在作用域结束时为 temp 调用析构函数。

最终方法 1 更有效,但这是依赖于编译器的功能。

于 2013-06-17T19:49:21.750 回答
0

第二种实现将导致 NRV 优化。Stan Lippman 说 NRV 优化需要一个显式的复制构造函数,但是这里的类 X 很简单,所以我认为 NRV 不需要显式的复制构造函数。

于 2013-06-17T20:10:46.467 回答