首页 诗词 字典 板报 句子 名言 友答 励志 学校 网站地图
当前位置: 首页 > 教程频道 > 开发语言 > 编程 >

暂时变量带给你的困惑

2012-12-27 
临时变量带给你的困惑返回值优化(RVO),顾名思义,就是与返回值有关的优化,是当函数是按值返回(而不是引用啊

临时变量带给你的困惑

返回值优化(RVO),顾名思义,就是与返回值有关的优化,是当函数是按值返回(而不是引用啊、指针)时,为了避免产生不必要的临时对象以及值拷贝而进行的优化。

先看看下面的代码:

class MyCla

{

public:

??? MyCla(int a_size = 10):size(a_size) {

??????? p = new int[size];???????

??? }

??? MyCla(MyCla const & a_right):size(a_right.size) {

??????? p = new int[size];

??????? memcpy(p, a_right.p, size*sizeof(int));

??? }

??? MyCla const& operator = (MyCla const & a_right) {

??????? size = a_right.size;

??????? p = new int[size];

??????? memcpy(p, a_right.p, size*sizeof(int));

??????? return *this;

??? }

??? ~MyCla() {

??????? delete [] p;

??? }

private:

??? int *p;

??? int size;

};

MyCla TestFun() {

??? return MyCla();

}

int _tmain(int argc, _TCHAR* argv[])

{

??? MyCla a = TestFun();

??? return 0;

}

TestFun() 函数返回了一个 MyCla 对象,而且是按值传递的。

在没有任何“优化”之前,这段代码的行为也许是这样的:return MyCla() 这行代码中,构造了一个 MyCla 类的临时的无名对象(姑且叫它t1),接着把 t1 拷贝到另一块临时对象 t2(不在栈上),然后函数保存好 t2 的地址(放在 eax 寄存器中)后返回,TestFun 的栈区间被“撤消”(这时 t1 也就“没有”了,t1 的生存域在 TestFun 中,所以被析构了),在 MyCla a = TestFun(); 这一句中,a 利用 t2 的地址,可以找到 t2 进行,接着进行构造。这样 a 的构造过程就完成了。然后再把 t2 也“干掉”。

可以看到,在这个过程中,t1 和 t2 这两个临时的对象的存在实在是很浪费的,占用空间不说,关键是他们都只是为a的构造而存在,a构造完了之后生命也就终结了。既然这两个临时的对象对于程序员来说根本就“看不到、摸不着”(匿名对象嘛,你怎么引用?),于是编译器干脆在里面做点手脚,不生成它们!怎么做呢?很简单,编译器“偷偷地”在我们写的fun函数中增加一个参数 A&,然后把 a 的地址传进去(注意,这个时候 a 的内存空间已经存在了,但对象还没有被“构造”,也就是构造函数还没有被调用),然后在函数体内部,直接用 a 来代替原来的“匿名对象”,在函数体内部就完成 a 的构造。这样,就省下了两个临时变量的开销。这就是所谓的“返回值优化”~!在 VC7 里,按值返回匿名对象时,默认都是这么做。

上面说的是“返回值优化(RVO)”,还有一种“具名返回值优化(NRVO)”,是对于按值返回“具名对象”(就是有名字的变量!)时的优化手段,其实道理是一样的,但由于返回的值是具名变量,情况会复杂很多,所以,能执行优化的条件更苛刻,在下面三种情况下(来自MSDN),NRVO 将一定不起作用:

1.??????? 不同的返回路径上返回不同名的对象(比如if XXX 的时候返回x,else的时候返回y)

2.??????? 引入 EH(exception handling) 状态的多个返回路径(就算所有的路径上返回的都是同一个具名对象)

3.??????? 在内联asm语句中引用了返回的对象名。

不过就算 NRVO 不能进行,在上面的描述中的 t2 这个临时变量也不会产生,对于VC的 C++编译器来说,只要你写的程序是把对象按值返回的,它会有两种做法,来避免 t2 的产生。拿下面这个程序来说明:

MyCla TestFun2() {

??? MyCla x(3);

??? return x;

}

一种做法是像 RVO一样,把作为表达式中获取返回值来进行构造的变量 a 当成一个引用参数传入函数中,然后在返回语句之前,用要返回的那个变量来拷贝构造 a,然后再把这个变量析构,函数返回原调用点,a 就构造好了。

还有一种方式,是在函数返回的时候,不析构 x ,而直接把 x 的地址放到 exa 寄存器中,返回调到 TestFun2 的调用点上,这时,a 可以用 exa 中存着的地址来进行构造,a 构造完成之后,再析构原来的变量 x !是的,注意到其实这时,x 的生存域已经超出了 TestFun2,但由于这里 x 所在 TestFun2 的栈虽然已经无效,但是并没有谁去擦写这块存,所以 x 其实还是有效的,当然,一切都在汇编的层面,对于 C++ 语言层面来讲是透明的。

热点排行