详解RVO与NRVO(区别于网上常见的RVO)

一直以来对 RVO 与 NRVO 以及编译器的优化操作之间的关系都不太分得清。这一次想了两天,查看了若干资料以后,总算弄清楚了。


1.RVO(Return Value Optimization)

先来看一下维基百科上对RVO(return value optimization) 的定义:

"Return value optimization, or simply RVO, is a compiler optimization technique that involves eliminating the temporary object created to hold a function's return value.[1] " 链接见:return value optimization

个人的理解为(翻译不当请见谅):返回值优化,简称RVO,是编译器的一项优化技术,它涉及(功能是)消除为保存函数返回值而创建的临时对象

也即是说RVO的功能是消除函数返回时创建的临时对象。如下例,

class X
{
public:
	X(){ std::cout << "construct" << std::endl; }
	X(const X & x){ std::cout << "copy" << std::endl; }
};

X func()
{
	X x;
	return x;
}

int main()
{
	X xs = func();
}

按照《C++ primer 》第四版第214页所说:“如果返回类型不是引用,在调用函数的地方会将函数返回值复制给临时对象”。也就是说,此时会调用一个构造函数,两次复制构造函数。分别是:

1)构造函数:func()函数中局部对象的构造

2)第一次构造函数:在函数的调用地方,将函数返回值 x 复制给临时对象temp

3)第二次构造函数:将临时对象 temp 的值复制给对象 xs


RVO的功能即是需要消除此时的 temp 对象的生成。在《深度探索C++对象模型》第64页说了编译器对非引用类型的对象编译器的处理方式(编译器优化)。此时编译器会将返回值函数的原型进行调整,提供一个接口,即将上述调用与函数转换为如下:

void func(X & result)
{
	X x;
        result.X::X(x);  //复制构造函数
	return;
}

int main()
{
	X xs;     //仅定义不构造
	func(xs);
}
经过这种转换后,编译器将只调用一次构造函数和一次复制构造函数。操作是:直接将需要赋值的对象作为引用传入函数中,并在函数返回前,给该对象进行构造

1)构造函数:func()函数中局部对象的构造

2)复制构造函数:在 func() 返回前用局部对象 x 的值来复制构造传入的 xs 对象(引用对象)


此时可以很明显看出通过该方法,消除了为保存返回值而创建的临时对象,因为此时函数根本没有返回值(返回类型变为 void)。根据维基百科对 RVO 的定义,此时这种编译器优化方式就属于 RVO 了。


VS 2013执行原始代码将仅调用一次构造函数,一次复制构造函数。因此,推测 VS2013 进行了上述 RVO 优化。

在 G++ 上编译时关闭 RVO ,即,使用指令“g++ -o rvo_test rvo_test.cc -fno-elide-constructors”,此时原始代码将调用一次构造函数,两次复制构造函数(VS不知道怎么关闭RVO),符合上述描述。


2.NRVO(Named Return Value Optimization)

NRVO是另一种优化技术,也消除了返回值产生的临时对象,根据维基百科的定义,也属于 RVO 的一种技术。其具体操作如下例,将上述代码优化为:

void func(X & result)      // void func()         //修改了函数原型
{                          //{  
	X result;          //     X x;           //使用result直接替换返回对象x 进行操作
	return;            //     return x
}                          // }

int main()
{
	X xs;     //仅定义不构造
	func(xs);
}
如上的例子中,NRVO的优化比RVO 的优化更进一步,直接将要初始化的对象替代掉返回的局部对象进行操作。可以看出,进行NRVO 的优化后,此时整个函数将会只调用一次构造函数。

1)构造函数:构造传入的引用对象

在 G++ 中,如果不设置关闭 RVO,则g++将会执行RVO,在g++编译器上运行原始函数,将会仅调用一次构造函数。


3.大家口中的RVO

先前造成我困惑的原因大部分是因为网上大家说的RVO,在网上大部分的人认为RVO 是针对如下函数形式:

X func()
{
	return X();
}

int main()
{
	X xs = func();
}

即返回的非引用对象是一个临时对象,此时将会同样进行上述类似NRVO 的优化,只是此时引用对象将不用替换任何局部变量,直接用构造临时对象的参数来构造引用对象。即优化为如下形式:

void func(X & result)
{
        result.X::X();  //用构造临时对象的参数构造引用对象
	return;
}

int main()
{
	X xs;     //仅定义不构造
	func(xs);
}
此时的RVO 是 对应 NRVO而言的,即认为 RVO 是NRVO中优化的对象并没有 Named ,是一个临时对象。但是按照维基百科的定义,这种优化方式应该只是 RVO 的一种。而不是说这个优化就是 RVO了。网上大家认为的RVO 与 NRVO 事实上应该是没有什么关系的,是两种RVO 的方式,前者的方式是使用临时对象的构造参数用来构造引用对象;后者的方式是用引用对象替换返回对象进行操作。


总结:

1)RVO 是一项编译器优化技术,这种技术将消除返回非引用对象在调用处创建的临时对象。

2)上述说的第一种编译器优化方法,NRVO,网上大家说的RVO,这三种技术其实都属于 RVO技术。

你可能感兴趣的:(C++对象模型_读书笔记,C++知识点)