版权归原作者所有,如有侵权,请联系我们

[科普中国]-返回值优化

科学百科
原创
科学百科为用户提供权威科普内容,打造知识科普阵地
收藏

返回值优化(Return value optimization,缩写为RVO)是C++的一项编译优化技术,即删除保持函数返回值的临时对象。这可能会省略两次复制构造函数,即使复制构造函数有副作用。典型地,当一个函数返回一个对象实例,一个临时对象将被创建并通过复制构造函数把目标对象复制给这个临时对象。C++标准允许省略这些复制构造函数,即使这导致程序的不同行为,即使编译器把两个对象视作同一个具有副作用。

简介返回值优化,是一种属于编译器的技术,它通过转换源代码和对象的创建来加快源代码的执行速度。当函数需要返回一个对象的时候,如果自己创建一个临时对象用户返回,那么这个临时对象会消耗一个构造函数(Constructor)的调用、一个复制构造函数的调用(Copy Constructor)以及一个析构函数(Destructor)的调用的代价。而如果稍微做一点优化,就可以将成本降低到一个构造函数的代价,这样就省去了一次拷贝构造函数的调用和依次析构函数的调用。

构造函数与析构函数构造函数,是一种特殊的方法。主要用来在创建对象时初始化对象,即为对象成员变量赋初始值,总与new运算符一起使用在创建对象的语句中1。特别的一个类可以有多个构造函数,可根据其参数个数的不同或参数类型的不同来区分它们即构造函数的重载。多数编程语言允许构造函数重载,一个类被允许拥有多个接受不同参数种类的构造函数同时存在。一些编程语言允许某些特殊种类的构造函数。使用单个类来具体地建立和返回新实例的构造函数,时常被抽象为工厂方法 - 一种同样用来建立新对象,但会同时使用多个类,或者一些诸如对象池的分配方案来完成这一过程的子程序。构造函数类型有参数化构造函数、默认构造函数。

接收参数的构造函数被称为参数化构造函数。参数的数量可以大于或等于一。如果在编写一个可实例化的类时没有专门编写构造函数,多数编程语言会自动生成缺省构造函数。

缺省构造函数的特性依不同语言而定。某些情况下它会将所有的实例变量同时初始化到0,或者任何其他别的值;某些缺省构造函数什么也不会做。某些语言 (Java, C#, VB .NET) 会缺省构造由该类类型定义的数组,使其充满空值引用。没有空值引用的语言一般会禁止缺省构造包含不可缺省构造对象的数组,或者要求在建立时专门初始化这些数值。

析构函数(destructor) 与构造函数相反,当对象结束其生命周期时(例如对象所在的函数已调用完毕),系统自动执行析构函数。析构函数往往用来做“清理善后” 的工作(例如在建立对象时用new开辟了一片内存空间,delete会自动调用析构函数后释放内存)。对于构造函数应注意:不能在结构中定义析构函数、只能对类使用析构函数、一个类只能有一个析构函数、无法继承或重载析构函数、无法调用析构函数,它们是被自动调用的,析构函数既没有修饰符,也没有参数。

以C++语言为例:析构函数名也应与类名相同,只是在函数名前面加一个位取反符~,例如~stud( ),以区别于构造函数。它不能带任何参数,也没有返回值(包括void类型)。只能有一个析构函数,不能重载。如果用户没有编写析构函数,编译系统会自动生成一个缺省的析构函数(即使自定义了析构函数,编译器也总是会为我们合成一个析构函数,并且如果自定义了析构函数,编译器在执行时会先调用自定义的析构函数再调用合成的析构函数),它也不进行任何操作。所以许多简单的类中没有用显式的析构函数。

优化示例对于函数返回类对象,一种实现办法是在函数调用语句前在stack frame上声明一个隐藏对象,把该对象的地址隐蔽传入被调用函数,函数的返回对象直接构造或者复制构造到该地址上。

struct Data { char bytes[16]; };Data f() { Data result = {}; // generate result return result;}int main() { Data d = f();}可能产生的代码如下:

struct Data { char bytes[16]; };Data * f(Data * _hiddenAddress) { Data result = {}; // copy result into hidden object *_hiddenAddress = result; return _hiddenAddress;}int main() { Data _hidden; // create hidden object Data d = *f(&_hidden); // copy the result into d}这引起了Data对象被复制两次。

另一种技术是命名返回值优化(Named return value optimization,NRVO)。[5]NRVO去除了基于栈的返回值的构造与析构。虽然这会导致优化与未优化的程序的不同行为。

struct Data { char bytes[16]; };void f(Data *p) { // generate result directly in *p}int main() { Data d; f(&d);}大部分C++编译器均支持返回值优化。在某些环境下,编译器不能执行此优化。一个常见情形是当函数依据执行路径返回不同的命名对象,或者命名对象在asm内联块中被使用:

#include struct C { C(int j) { i = j; } C(const C&) { std::cout