#include <iostream>
using namespace std;
class A {
public:
A() {
cout << "default constructor" << endl;
}
A(const A& x) {
cout << "copy constructor" << endl;
}
A(A&& x) {
cout << "move constructor" << endl;
}
A& operator = (const A& x) {
cout << "copy assigment operator" << endl;
return *this;
}
A& operator = (A&& x) {
cout << "move assigment operator" << endl;
return *this;
}
};
A returnValue() {
return A();
}
A returnValue_1() {
A a;
return a;
}
A&& returnValue_2() {
return A();
}
int main() {
A e = returnValue(); // move constructor
cout << endl;
A e1 = returnValue_1(); // move constructor
cout << endl;
A e2 = returnValue_2(); // move constructor
return 0;
}
/*
default constructor
default constructor
move constructor
default constructor
move constructor
A e = returnValue();只打印了一句,我理解执行A()
打印了 default constructor ,然后 return 时经过 RVO 优化,就少打印一次。然后经过 https://en.cppreference.com/w/cpp/language/copy_initialization 里面提到的 纯右值的优化,又少打印一次。
A e2 = returnValue_2();打印两次是因为:执行A()
打印了 default constructor ,然后 return 时经过 RVO 优化,就少打印一次。但由于函数返回值类型为 A&&,作为 亡值,就必须调用 移动构造函数。
但 A e1 = returnValue_1();为什么打印这两句阿?(它的返回值类型就是 A ,不是 A&&啊)
顺便问下,移动赋值操作符这里的提示是啥意思呀
(上面说的理解,如果有问题也请指正)
1
nlzy 2022-01-24 19:23:22 +08:00 via Android
|
2
lovelylain 2022-01-24 19:30:50 +08:00
returnValue_1 是 NRVO ,Named Return Value Optimization 具名返回值优化,简单说就是编译器会尽可能给你做返回值优化,减少拷贝。
|
3
amiwrong123 OP |
4
v2byy 2022-01-24 21:19:54 +08:00
@amiwrong123 使用 vs2019 release 模式下,确实 returnValue1 只调用了 default ctor
|
5
woodpenker 2022-01-24 21:33:28 +08:00
编译器无法确定一定能优化 NRVO 场景, 只能先放栈帧中再移出到调用方. 优化过的编译器识别到这种只有一个确定的命名返回值时就可以把 move 优化掉.
|
6
amiwrong123 OP @v2byy #4
真的哎,release 模式 居然不一样,这是为啥 |
7
ysc3839 2022-01-24 21:48:29 +08:00 via Android
@amiwrong123 因为 Debug 禁用了优化。
比如你这段代码,Release 下的优化可能直接把 cout << "default constructor" << endl; 内联到 main 里面了,调试时你可能想 step into returnValue_1(),结果发现直接“跳过”了这个函数,Debug 模式会禁用优化,便于调试。 |
8
statumer 2022-01-24 22:03:59 +08:00 2
建议你了解一下 copy elision
对哪些优化是强制性,哪些优化是非强制性的解释得很明确了。 现在你写的第一种是标准要求的 guaranteed copy elision 了,满足 copy elision 规则所以只调用默认构造函数(并不是两次优化)。 你写的第二种是非强制的 copy elision ,所以编译器可以自行决定是 RVO 还是 copy/move 。 你写的第三种应该是不合法的,A()是临时对象会被分配到栈上,returnValue_2 返回了一个栈上对象的引用。严重时会导致段错误。 https://en.cppreference.com/w/cpp/language/copy_elision |
9
amiwrong123 OP @statumer #8
> 现在你写的第一种是标准要求的 guaranteed copy elision 了,满足 copy elision 规则所以只调用默认构造函数(并不是两次优化)。 那是不是也可以理解为 有两次 copy elision ,return 时一次,初始化变量时一次。 >你写的第三种应该是不合法的,A()是临时对象会被分配到栈上,returnValue_2 返回了一个栈上对象的引用。严重时会导致段错误。 我记得不是有一种,引用可以延长临时变量生命周期的东西?这样是不是就是合法的 了。 另外,看 cppreference 总感觉有些话看不懂: >即使复制 /移动构造函数和析构函数拥有可观察的副作用 这句啥意思阿,啥叫可观察的副作用😂 struct C { /* ... */ }; C f(); struct D; D g(); struct D : C { D() : C(f()) { } // 初始化基类子对象时无消除 D(int) : D(g()) { } // 无消除,因为正在初始化的 D 对象可能是某个其他类的基类子对象 }; >无消除,因为正在初始化的 D 对象可能是某个其他类的基类子对象 它是说 D(g())这里吗,这里看起来是调用了一个 委托构造函数,正在初始化的 D 对象怎么可能是某个其他类的基类子对象呢?( D 也没有派生类阿。。) |
10
agagega 2022-01-26 00:50:38 +08:00
@amiwrong123
可观察的副作用就是说有一个操作在那,限制了你没法做优化。举例:妈妈让小明下楼走一圈,小明可以出门坐一会然后给妈妈说自己走过了;但如果妈妈让小明下楼走一圈并买瓶酱油,小明就没有办法偷懒必须得下去了。 |