C++返回值优化
发现
#include <iostream>
using namespace std;
class Test {
public:
Test() { cout << this << " default constructor" << endl; }
Test(const Test &that) { cout << this << " copy constructor" << endl; }
~Test() { cout << this << " deconstructor" << endl; }
};
Test func() {
Test temp;
return temp;
}
int main(int argc, char const *argv[]) {
Test a = func();
return 0;
}
上述代码的调用流程应该是这样的:
- 调用temp 的构造函数
- 函数返回值会调用临时对象复制构造函数
- 调用a的复制构造函数
- 调用返回的临时对象的析构函数
- 调用a的析构函数
但实际的输出却是这样的:
0x61fe0f default constructor
0x61fe0f deconstructor
经过一步步调试发现,其只进行了上述调用流程的第一步和最后一步。
为什么呢?查询资料后发现,这是C++的一项编译优化技术,称为返回值优化,即并不进行返回值构造临时对象这一过程,这样也就不会调用复制构造函数了。
若需要,我们可以加上-fno-elide-constructors
这一编译选项来关闭这一优化(该选项的详细说明可以在Linux终端通过man gcc
或man g++
命令来查看)。
在终端输入以下命令:
g++ -g -o test -fno-elide-constructors test.cpp
./test
我们可以得到以下输出:
0x61fbaf default constructor
0x61fd3f copy constructor
0x61fbaf destructor
0x61fd3e copy constructor
0x61fd3f destructor
0x61fd3e destructor
该输出符合上述调用流程。
另外,函数返回值其实是优先调用移动构造函数的。若定义了移动构造函数,它将调用移动构造函数而非复制构造函数。当然,若我们自己并未定义移动构造函数,它也就只能调用复制构造函数了。
我们在代码中加入移动构造函数:
Test(Test &&that) { cout << this << " move constructor" << endl; }
并关掉返回值优化,则运行结果为:
0x61fbaf default constructor
0x61fd3f move constructor
0x61fbaf destructor
0x61fd3e move constructor
0x61fd3f destructor
0x61fd3e destructor
实现
栈帧提前构造
对于返回值优化,一种实现方法是在调用函数(caller)在栈帧上声明一个隐藏对象,然后将其地址隐蔽地传入被调用函数(callee),被调用函数的返回值直接构造到该地址上。如下所示:
#include <iostream>
using namespace std;
class Test {
public:
Test() { cout << this << " default constructor" << endl; }
Test(const Test &that) { cout << this << " copy constructor" << endl; }
// Test(Test &&that) { cout << this << " move constructor" << endl; }
~Test() { cout << this << " destructor" << endl; }
};
Test* func(Test* hidden) {
Test temp;
*hidden = temp;
return hidden;
}
int main(int argc, char const *argv[]) {
Test hidden;
Test a = *func(&hidden);
return 0;
}
注意:由于第14行是需要调用赋值运算符的,所以我们这里将移动构造函数注释了。(移动构造函数与赋值运算符不能共存)
输出如下:
0x61fd3f default constructor
0x61fc7f default constructor
0x61fc7f destructor
0x61fd3e copy constructor
0x61fd3e destructor
0x61fd3f destructor
可见这种方法优化并不明显,仅是将一个复制构造函数换成了普通构造函数。
命名返回值优化
另一方法是命名法返回值优化,其去除了基于栈的构造与析构,而是直接将返回值移进目标对象的地址。如下所示:
#include <iostream>
using namespace std;
class Test {
public:
Test() { cout << this << " default constructor" << endl; }
Test(const Test &that) { cout << this << " copy constructor" << endl; }
// Test(Test &&that) { cout << this << " move constructor" << endl; }
~Test() { cout << this << " destructor" << endl; }
};
void func(Test *target) {
Test temp;
*target = temp;
}
int main(int argc, char const *argv[]) {
Test a;
func(&a);
return 0;
}
输出如下:
0x61fd3f default constructor
0x61fc8f default constructor
0x61fc8f destructor
0x61fd3f destructor
这一方法的效率已经很高了,但还是与实际编译器的输出略有不同,或许实际中编译器或许进行了更多更复杂的优化吧。
参考
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!