条款21:必须返回对象时,别妄想返回其reference。

来源:互联网 发布:淘宝卖蜂蜜选什么类目 编辑:程序博客网 时间:2024/04/29 05:36
前一小节已经讨论过,pass by vaule的代价有时候是巨大的,pass by refrence比较方便。那么肯定也有人会立刻想到,函数返回值的时候,能不能也采用这种办法来提高程序的效率呢?

为了能够简单且说明问题,这里选择了对于内置类型返回其reference:

int& func(){int i=3;return i;}void doNothing(int i){}int main(){int& k=func();doNothing(7); //注释此行,看看有何区别cout <<k <<endl;return 0;}

首先,返回局部变量的引用是不对的。因为如果你返回一个引用,引用肯定是一个对象的别名,那么你一定要先反问自己:这个引用所代表的实际值到底是谁呢?想到这里,我们就一目了然了:你返回的引用是局部变量i的别名,但是i在函数结束以后,就被释放了!所以此时的返回的是一个垃圾数字。这个程序奇怪的地方在于,如果把doNothing函数注释掉,那么程序竟然能得出正确的值,这里的奥秘在于如果调用了函数,那么栈的内容有可能发生改变,改变之后,程序就得出合理的错误值了。而当这个函数调用被注释掉以后,由于栈的内容没有改变,所以即使i已经被释放了,但是它并没有被清零,值还是3,我还是可以引用它。类似的例子还有就是返回局部变量的指针:

int* func(){int i=3;int *p = &i;return p;}int main(){int *p = func();doNothing(1);cout<<*p<<endl;return 0;}

也许,有人会想到,那么我让p指针时堆上分配的内存不就行了?

int* func(){int *p = new int(3);return p;}

但这样会则要求函数的调用者手动释放内存,否则会造成内存泄露。最致命的是,有时这种泄露是无法被释放的!
比如下面这个计算有理数的类:

class Rational{public:Rational(int numerrator = 0, int denominator = 1):n(numerrator),d(denominator){}double getVal(){double result = (double)n / double(d);return result;}friend Rational& operator*(const Rational &lhs,const Rational &rhs);private:int n;//分子int d;//分母};Rational& operator*(const Rational &lhs,const Rational &rhs){Rational* result = new Rational(lhs.n*rhs.n,lhs.d*rhs.d);return *result;}

如果我这样调用:

Rational t1(1,1);Rational t2(1,2);Rational t3(1,5);Rational t4 = t1*t2*t3;

那么t2*t3所new的指针就没办法释放了!


也许有人自作聪明的想到,让返回的引用指向一个定义域函数内部的static对象:

Rational& operator*(const Rational &lhs,const Rational &rhs){static Rational result;result = Rational(lhs.n*rhs.n,lhs.d*rhs.d);return result;}

假如你又定义了比较操作符:

bool operator==(const Rational &rhs){return n == rhs.n && d == rhs.d;}

但是如果你很快就会发现:if( (t1 * t2) == (t3 * t4))这样的语句总是为true。
原因在于(t1 * t2)会修改reslt的值,而(t3 * t4)又会修改result的值。总之,你始终是在做result == result 的判断,当然为true了。

总结起来就是:绝不要返回一个指向局部对象的指针或者引用,也不要返回指向分配在堆上的对象,也不要返回全局或者静态对象。返回值还是pass by value比较好。

原创粉丝点击