为什么复制省略会使形式参数例外
Why does copy elision make an exception for formal parameters?
这里有一个完整的程序:
#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
。
复制省略※的摘要陈述了"这不是函数参数",这让我认为VS是错误的。这是正确的吗?
但是,为什么在这个规则中对形式参数名称进行特殊处理?为什么它和其他局部变量不一样
(我并不是说优化器会根据调用约定以及调用者和调用者ee的单独编译来解决问题,而只是为什么不允许。)
编辑:如果输出1
是正确的,那么它如何符合省略规则?
注※:我发现该文本是从公开的N3690中的§12.8第31段中复制的。
首先,要明白RVO和NRVO是标准编写者向编译器编写者提供的机会。给定的编译器可以自由忽略RVO或NRVO的可能性,如果它不能使其工作,如果它不知道它是否能使其工作、如果月亮是圆的,等等。
不过,在这种情况下,这很容易。(N)RVO的基本实现方式是通过将返回值直接构造到由返回值占用的存储器中,或者甚至构造到将被设置为该返回值的变量占用的存储器。
也就是说,(N)RVO的潜在节约不仅来自于消除复制构造的能力,还来自于总体上减少复制的能力。
但是,当返回值的来源是一个函数参数时,就太晚了。left
已经在内存中的某个位置,返回值必须转到其他位置。由于已经构造了第二个对象,所以复制已经是一种给定的方法,而不是像内联一样的暴力。
如果复制省略被禁用,那么这两种情况都由1次复制和3次移动组成。(代码的2
的任何输出都表明存在编译器错误)。
副本为:
left
的初始化
动作是:
- 初始化
left
的返回值 - 从返回值初始化由
a+b
表示的临时对象 - 从
a+b
初始化result
将count
替换为表示"in copy constructor"
和"in move constructor"
的输出消息将更具启发性。目前您根本没有跟踪移动。
在通过参考的情况下,所有3个移动都可以被忽略。在传递值的情况下,可以忽略其中的2个移动。无法消除的移动是从left
移动到返回值。
我不知道为什么不能取消这一举措的理由。如果A()
一直到a
都是可省略的,那么编译器可能很难做像A a = foo( A() );
这样的事情。
- 当从函数参数中的临时值调用复制构造函数时
- 如果有一个模板构造函数只有一个泛型参数,为什么我必须有一个复制构造函数
- 为什么默认复制函数在按值发送参数时不调用?
- std::p ackaged_task 应该删除带有 const 参数的复制 c'tor
- 将参数传递给泛型 lambda 时复制构造函数不正确
- 如何确定捕获不可复制参数的 lambda 的类型?
- 复制 avcodec 参数
- 从函数参数 [C++] 复制整数数组
- 候选构造函数(隐式复制构造函数)不可行:第一个参数需要 l 值
- 使用另一个类中的参数复制构造函数
- 在 c++ 中从参数复制本地数组比从数组更快?
- 是否可以避免将参数复制到 lambda 函数?
- 可以使用默认参数复制包含 lambda 的 std::函数吗?
- C 指针节点帮助 - 将参数复制到链接的列表节点更改该节点的不同部分
- gcc 的 std::bind 在源中的哪个位置将参数复制到数据结构中?
- std::绑定参数复制行为
- 使用参数复制构造函数作为对派生类的引用
- 将指针类参数复制到类成员的好处
- 使用 2 参数复制构造函数
- 函数参数:复制或指针