这是一个完整的程序:
#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+
要考虑。
inline Triple operator+(const Triple& lhs, const Triple& rhs) {
Triple left { lhs };
left += rhs;
return left;
}
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段复制的。
答案 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
之类的事情。