记得自己之前在哪写过一篇返回值优化的博客,翻了半天csdn,居然没找到,也不知道写在哪了。被问到一个unique_ptr 局部变量能不能做返回值的问题,想当然说因为拷贝构造被delete不行,果然我还是unique_ptr 用的不多,只用来pimpl了。想起来真是被自己蠢笑了。
先了解一下左右值、将亡值的概念,概念就不抄了
class A {
public:
int a;
};
A getTemp()
{
return A(); //右值
}
A a = getTemp();//a左值,gettemp()返回值右值
可以看到,按照理想情况,我们其实会一次构造加+两次拷贝构造,还有多个临时对象的析构,但是我们只要实际长期使用的只是为了为了构建一个a变量使用而已,花销太大,因此c++11 引入了移动语义以及RVO机制(RVO c++11 编译器都有,但并不是是语言标准强规范,c++17起才在规范做了强制要求),减少无用的拷贝构造和析构。
跑个实际的例程吧
#include
#include
using namespace std;
class Test {
public:
Test() {
cout << "Create" << endl;
}
Test(const Test &o) {
cout << "Copied" << endl;
}
Test(const Test &&o) {
cout << "Move" << endl;
}
~Test() {
cout << "Destroy" << endl;
}
};
Test ReturnRvalue() {
return Test(); //RVO优化,函数返回值的地址就是test右值分配的地址,没有做赋值及移动拷贝
}
Test ReturnRvalue1() {
auto temp = Test();
return temp; //NRVO优化,函数返回值的地址就是test右值的地址,没有做赋值及移动拷贝
}
void AcceptVal(Test a) {}
void AcceptRef(const Test &a) {}
int main() {
cout << "pass by value: " << endl;
AcceptVal(ReturnRvalue());
cout << "pass by value1: " << endl;
AcceptVal(ReturnRvalue1());
cout << "pass by reference: " << endl;
AcceptRef(ReturnRvalue());
}
输出如下,节省了复制/移动拷贝,只进行了一次构造
pass by value:
Create
Destroy
pass by value1:
Create
Destroy
pass by reference:
Create
Destroy
接下来禁用返回值优化 -fno-elide-constructors (gcc选项,msvc没找到)
pass by value:
Create
Move
Destroy
Move
Destroy
Destroy
pass by value1:
Create
Move
Destroy
Move
Destroy
Move
Destroy
Destroy
pass by reference:
Create
Move
Destroy
值得注意的是,返回值调用在例程中仍然调用的是移动构造
Test ReturnRvalue1() {
auto temp = Test();
return temp; //禁用RVO仍是调用移动构造
}
这是也是c++11引入的一个优化,但查了下他居然不叫返回值优化。
字面意思就是,某些自动存储周期(函数参数、函数体)的局部变量做返回值时,重载决议两次,先移动构造,再拷贝构造。
所以即使不考虑RVO 以下两种都是语法上都是合法的
std::unique_ptr<int> questionA(){
auto t = make_unique<int>(99);
return t;
}
std::unique_ptr<int> questionB(std::unique_ptr<int> t){
return t;
}