如何返回一个对象?
一个用来返回的对象,通常应当是可移动构造 / 赋值的,一般也同时是可拷贝构造 / 赋值的。如果这样一个对象同时又可以默认构造,我们就称其为一个半正则(semiregular)的对象。如果可能的话,我们应当尽量让我们的类满足半正则这个要求。
1.返回值优化(拷贝消除)
下面编译的gcc版本是支持c 17的gcc8.3。如果使用gcc5.5等版本结果会不同。
ROV例子1:
代码语言:javascript复制#include <iostream>
using namespace std;
// Can copy and move
class A {
public:
A() { cout << "Create An"; }
~A() { cout << "Destroy An"; }
A(const A &) { cout << "Copy An"; }
A(A &&) { cout << "Move An"; }
};
A getA_unnamed() {
return A();
}
int main() {
auto a = getA_unnamed();
}
返回值优化输出:
代码语言:javascript复制Create A
Destroy A
禁用返回值优化代码修改(-fno-elide-constructors):
代码语言:javascript复制Create A
Destroy A
修改代码如下NROV例子2:
代码语言:javascript复制A getA_named()
{
A a;
return a;
}
int main()
{
auto a = getA_named();
}
优化结果同上!
代码语言:javascript复制Create A
Destroy A
禁用后的结果:
代码语言:javascript复制Create A
Move A
Destroy A
Destroy A
也就是说,返回内容被移动构造了。
示例3:
代码语言:javascript复制A getA_duang() {
A a1;
A a2;
if (rand() > 42) {
return a1;
}
else {
return a2;
}
}
优化与禁用结果一样:
代码语言:javascript复制Create A
Create A
Move A
Destroy A
Destroy A
Destroy A
我们把移动构造删除,得到示例4:
代码语言:javascript复制A(A&&)= delete;
此时,前面的move就变成copy。
如果再进一步,把拷贝构造函数也删除呢?得到示例5:
代码语言:javascript复制A(const A&&)= delete;
A(A&&)= delete;
是不是上面的 getA_unnamed
、getA_named
和 getA_duang
都不能工作了?
在 C 14 及之前确实是这样的。但从 C 17 开始,对于类似于 getA_unnamed
这样的情况,即使对象不可拷贝、不可移动,这个对象仍然是可以被返回的!C 17 要求对于这种情况,对象必须被直接构造在目标位置上,不经过任何拷贝或移动的步骤。
2.总结
- copy construct本身在RVO和NRVO两种情况下被优化了,如果再加上move反而画蛇添足。
在 C 11 之前,返回一个本地对象意味着这个对象会被拷贝,除非编译器发现可以做返回值优化(named return value optimization,或 NRVO),能把对象直接构造到调用者的栈上。从 C 11 开始,返回值优化仍可以发生,但在没有返回值优化的情况下,编译器将试图把本地对象移动出去,而不是拷贝出去。这一行为不需要程序员手工用 std::move
进行干预——使用std::move
对于移动行为没有帮助,反而会影响返回值优化。”
- 加入了move assignment后,默认是调用move assignment而不是copy assignment