为什么copy elision会对正式参数进行例外处理?

时间:2015-11-18 21:33:02

标签: c++ visual-c++ copy-elision

这是一个完整的程序:

#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为案例1提供了2

This summary of copy elision 声明“不是函数参数”这让我觉得VS错了。这是对的吗?

但是,为什么在此规则中专门处理正式参数名称?为什么它与其他任何局部变量完全不同?

(我不是说优化器会根据调用约定以及根据调用者和call-ee的单独编译来解决问题,而仅仅是为什么它不是允许< / em>的。)

编辑:如果输出1是正确的,那么它如何符合省略规则?

注意※:我发现此文本是从公开的N3690中的§12.8第31段复制的。

2 个答案:

答案 0 :(得分:1)

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

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

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

但是当返回值的来源是函数参数时,为时已晚。 left已经在内存中,返回值必须转到其他地方。如果没有内联的一些蛮力,那么复制已经是一个给定的,因为已经构建了第二个对象。

答案 1 :(得分:1)

如果禁用了复制省略,则两个案例都包含1个副本,然后是3个移动。 (代码的2的任何输出都表示编译器错误。)

副本是:

  • 初始化left

并且行动是:

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

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

在传递引​​用的情况下,可以省略所有3个移动。在按值传递的情况下,可以省略2个移动。无法忽略的举措是从left转移到返回值

我不知道为什么不能采取这一行动的理由。如果A a = foo( A() );如果A()一直到a一直是可以的,那么编译器可能很难做N之类的事情。