C++ 返回值优化RVO
返回值优化(Return Value Optimization, 简称RVO)是通过对源代码进行转换、消除对象的创建来实现加速程序,提升程序性能的优化措施,通常是由编译器实现的优化。
按值返回
假设我们打算实现Complex类,用于实现复数。可以这样设计Complex类:
class Complex
{
// Complex addition operator
friend Complex operator+(const Complex&, const Complex&);
public:
// Default ctor
Complex(double r = 0.0, double i = 0.0)
: real(r), imag(i)
{}
// Copy ctor
Complex(const Complex& c)
: real(c.real), imag(c.imag)
{}
// Assignment operator
Complex& operator=(const Complex& c);
~Complex(){}
private:
double real;
double imag;
};
我们是如何实现友元函数operator+(const Complex&, const Complex&),以实现两个复数相加的?
通常,我们这样写:
// 注意operator+是重载函数, 不是Complex类成员函数
Complex operator+(const Complex& a, const Complex& b)
{
Complex retVal;
retVal.real = a.real + b.real;
retVal.imag = a.imag + b.imag;
return retVal;
}
应用时,将Comlex对象c1, c2, c3按如下方式进行操作:
c3 = c1 + c2;
如何将c1+c2的值放到c3呢?
如果没有任何优化,调用运算符operator+时调用Complex构造函数,构造retVal对象;返回给c3时,又调用Complex的拷贝构造函数。也就是说,会构造两次Complex对象。
返回值优化
针对上面这种情形,编译器有专门的优化方式:编译器创建一个临时__result对象,作为一个专门的参数,按引用传递方式将结果传递给Complex::operator+()。
也就是说,编译器会把重载函数operator+重写为另一个稍有不同的函数:
void Complex_Add(const Complex& __result, const Complex& c1, const Complex& c2)
{
...
}
此时,原来的源代码c3 = c1 + c2,会被转换成(伪代码):
struct Complex __tempResult; // 存储, 没有构造
Complex_Add(__tempResult, c1, c2); // 所有参数引用传递
c3 = __tempResult; // 反馈结果给c3
这就是所谓返回值优化,编译器通过消除局部对象retVal并将其替换为__tempResult,优化Complex_Add()。
- 编译器不执行RVO通常情形
然而,并不是所有编译器会对这种返回值是临时对象的函数,执行RVO。通常,下面两种情况下,编译器不执行RVO:
1)复杂函数;
2)函数有多个return语句,并且返回不同名称的对象。
- 有一种编译器拒绝对以下特定版本operator+执行RVO
// operator+版本1
Complex operator+(const Complex& a, const Complex& b)
{
Complex retVal;
retVal.real = a.real + b.real;
retVal.imag = a.imag + b.imag;
return retVal;
}
// operator+版本2
Complex operator+(const Complex& a, const Complex& b)
{
Complex retVal(a.real + b.real, a.imag + b.imag);
return retVal;
}
- 编译器会对以下版本使用RVO
// operator+版本3
Complex operator+(const Complex& a, const Complex& b)
{
double r = a.real + b.real;
double i = a.imag + b.imag;
return Complex(r, i);
}
// operator+版本4
Complex operator+(const Complex& a, const Complex& b)
{
return Complex(a.real + b.real, a.imag + b.imag);
}
区别在于版本1、2为临时对象使用了命名变量,版本3、4使用匿名变量。
如果想让编译器为命名变量执行RVO,可以在返回时使用std::move,将左值转换为右值。
计算性构造函数
编译器无法执行RVO时,可以用计算性构造函数。比如上面Complex例子中,我们可以实现版本5的operator+,在其中创建一个默认的匿名Complex对象并推迟成员变量的设置,随后对对象成员进行初始化。这个构造函数,就是计算性构造函数。
// operator+版本5
Complex operator+(const Complex& a, const Complex& b)
{
return Complex(a, b);
}
// 计算性构造函数
Complex::Complex(const Complex& x, const Complex& y)
: real(x.real + y.real), imag(x.imag + y.imag)
{}
计算性构造函数缺点:必须为每种运算(如加、减、乘、除)符号,添加不同的计算性构造函数。
关闭RVO
可以通过编译选项-fno-elide-constructors
,关闭RVO功能。
参考
[1]DovBulka, DavidMayhew, 布尔卡,等. 提高C++性能的编程技术[M]. 清华大学出版社, 2003.
[2]C++返回值拷贝以及std::move | CSDN