1. return临时变量问题
#include<iostrea>
int a,i;
int fun()
{
int b=1,c=2;
a=b+c;
return a;
}
i=fun()
想问下,是a赋值给i,还是a的临时副本赋值给i?我试过了,会产生临时副本的。兄弟们,我的意思是问,既然a是全局变量了,又不会消失,怎么还会产生临时副本。如果a是局部变量 ,还好理解,因为a出了函数的作用域就会消失,所以系统会产生临时副本来代替a赋值等等的回答。
我来回答你的问题:
首先,这里必须是传递a的副本。
其次,为什么传递副本,不是由a的生命周期来定义的,比如a是否是全局,或者局部,甚至是static,都与是否传递副本没有关系的。
最后,这里传递副本是取决于fun函数的,fun函数返回的类型是 int,也就是说返回值的传递时采用”值传递”,你应该懂这个名词,不是地址传递,而值传递,明显特征是要传递副本;
换句话说,如果定义fun函数如下:
int& fun();
那么可以return a,不会有副本产生。注意必须是a为全局的情况下,否则会引起警告,禁止传递局部变量的引用,因为a的声明周期会马上结束掉。
到此,已经解答了楼主的问题。我想多说一句:为什么不写成int& fun();这种形式呢?答案是没必要。int是内部自定义类型。对于自定义变量而言,引用与否引起的副本拷贝带来的工作量不是很大,相反,如果是自定义类型,不知道楼主学习了class没有,那么引用返回就非常有必要了。因为带来的副本拷贝可能非常耗时,而这也是C++之父strup反复强调了,也是我们需要常常使用的。
2. return变量/对象,采用pass-by-value会产生临时变量,调用拷贝构造和析构
返回值优化(RVO)(无名返回值)
MyCla TestFun() {
return 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 里,按值返回匿名对象时,默认都是这么做。
具命返回值优化(NRVO)
MyCla TestFun2() {
MyCla x(3);
return x;
}
对于按值返回“具名对象”(就是有名字的变量!)时的优化手段,其实道理是一样的,但由于返回的值是具名变量,情况会复杂很多。
对于 C++ 编译器来说,只要你写的程序是把对象按值返回的,它会有两种做法,来避免 t2 的产生。
优化方案一
一种做法是像 RVO一样,把作为表达式中获取返回值来进行构造的变量 a 当成一个引用参数传入函数中,然后在返回语句之前,用要返回的那个变量来拷贝构造 a,然后再把这个变量析构,函数返回原调用点,a 就构造好了。
优化方案二
还有一种方式,是在函数返回的时候,不析构 x ,而直接把 x 的地址放到 exa 寄存器中,返回调到 TestFun2 的调用点上,这时,a 可以用 exa 中存着的地址来进行构造,a 构造完成之后,再析构原来的变量 x !是的,注意到其实这时,x 的生存域已经超出了 TestFun2,但由于这里 x 所在 TestFun2 的栈虽然已经无效,但是并没有谁去擦写这块存,所以 x 其实还是有效的,当然,一切都在汇编的层面,对于 C++ 语言层面来讲是透明的。