1

这是一个完整的程序:

#include <iostream>
using std::cout;
using std::endl;
using std::move;

int count {0};  // global for monitoring


class Triple {
public:
    Triple() = default;    // C++11 use default constructor despite other constructors being declared
    Triple(Triple&&) = default;
    Triple(const Triple& t) :    // copy constructor
        Triple(t.mX, t.mY, t.mZ) {
        count++;
    }

    Triple(const int x, const int y, const int z) :
        mX{ x }, mY{ y }, mZ{ z } {
    }

    const Triple& operator +=(const Triple& rhs) {
        mX += rhs.mX;
        mY += rhs.mY;
        mZ += rhs.mZ;
        return *this;
    }

    int x() const;
    int y() const;
    int z() const;

private:
    int mX{ 0 };    // c++11 member initialization
    int mY{ 0 };
    int mZ{ 0 };
};


#if 0
inline Triple operator+(const Triple& lhs, const Triple& rhs) {
    Triple left { lhs };
    left += rhs;
    return left;
}
#else
inline Triple operator+(Triple left, const Triple& rhs) {
    left += rhs;
    return left;
}
#endif

int main()
{
    Triple a,b;

    cout << "initial value of count is: " << count << endl;

    auto result { a+b };

    cout << "final value of count is: " << count << endl;
}

有趣的是复制构造函数有一个副作用,并且有两个版本operator+需要考虑。

情况1

inline Triple operator+(const Triple& lhs, const Triple& rhs) {
    Triple left { lhs };
    left += rhs;
    return left;
}

案例2

inline Triple operator+(Triple left, const Triple& rhs) {
    left += rhs;
    return left;
}

Visual Studio 2015 给出了相同的结果,打印结果为1. 但是 gcc 4.8.4 给出2了案例 1。

这个复制省略的总结※</sup> 声明“<em>这不是函数参数”,这让我认为 VS 是错误的。那是对的吗?

但是,为什么在这条规则中要特别对待形式参数名称呢?为什么它不完全像任何其他局部变量?

(我并不是说优化器根据调用约定并根据调用者和被调用者的单独编译来解决问题,而只是说为什么不允许这样做。)

编辑:如果输出1是正确的,这与省略规则有何关系?


注意※:我发现这段文字是从公开的N3690中的 §12.8 第 31 段复制而来的。

4

2 回答 2

1

如果禁用复制省略,则两种情况都包括 1 个副本和 3 个移动。(您的代码的任何输出2都表明存在编译器错误)。

副本是:

  • 初始化left

动作是:

  • 返回值的初始化left
  • a+b返回值表示的临时对象的初始化
  • result从初始化a+b

count用输出消息替换会更有启发性"in copy constructor"and "in move constructor"。目前,您根本没有跟踪移动。

在 pass-by-reference 的情况下,所有 3 个动作都可以省略。在按值传递的情况下,可以省略 2 个动作。不能省略的移动是从left返回值的移动。

我不知道为什么不能省略这一举动的理由。也许编译器很难做类似A a = foo( A() );if A()are elidable 一直到a.

于 2015-11-20T00:24:04.450 回答
1

首先,了解 RVO 和 NRVO 是标准编写者提供给编译器编写者的机会。一个给定的编译器可以自由地忽略 RVO 或 NRVO 的可能性,如果它不能让它工作,如果它不知道它是否可以让它工作,如果月亮是满的等等。

不过,在这种情况下,这很容易。(N)RVO的基本实现方式,是将返回值直接构造到返回值所占用的内存中,甚至是设置为该返回值的变量所占用的内存中。

也就是说,(N)RVO 的潜在节省不仅来自于省略复制构造的能力,还来自于总体上减少复制的能力。

但是当返回值的来源是函数参数时,就太迟了。left已经在内存中的某个地方,返回值必须去别的地方。缺少一些暴力的内联规则,复制已经是给定的,因为已经构造了第二个对象。

于 2015-11-19T23:59:01.280 回答