Effective C++条款21:设计与声明——必须返回对象时,别妄想返回其reference

  • 前面一篇文章我们介绍了,向函数中传递对象时,应以const引用的方式传递它,但是并不是在任何情况下使用引用都是正确的,文本来介绍一些情况

一、演示说明

  • 现在我们有一个用来变现有理数的class,内含一个函数用来计算两个有理数的乘积
  • 下面代码中的operator*是正确的,也是合理的
 
  1. class Rational

  2. {

  3. public:

  4. Rational(int numerator = 0, int denominator = 1);

  5. private:

  6. int n, d; //分子和分母

  7. friend const Rational

  8. operator*(const Rational& lhs, const Rational& rhs);

  9. };

错误实现:返回一个在栈上的局部变量

  • 下面我们修改operator*函数,使其在函数内部建立一个对象(栈上),然后返回其引用
  • 错误原因:由于result是局部对象,函数结束之后局部对象(栈空间)就释放了,引起返回其引用是无效的,也就是说该函数返回了一个已经无效的对象的引用,因此使用起来是错误的
 
  1. friend const Rational&

  2. operator*(const Rational& lhs, const Rational& rhs)

  3. {

  4. Rational result(lhs.n*rhs.n, lhs.d*rhs.d);

  5. return result; //虽然编译器不报错,但是逻辑上是错误的

  6. }

错误实现:返回一个在堆上的对象(内存泄漏)

  • 如果我们在堆上申请一个对象,然后将其引用返回,虽然编译不出错,但是使用起来也是错误的
  • 错误原因:在函数内,我们在堆上申请了一个对象,然后返回对象的引用,虽然函数结束之后对象仍存在,我们使用起来不会出错,但是这个函数内部建立的对象我们无法释放(因为我们无法获取其指针,不能进行delete操作)。因此会造成内存泄漏
 
  1. friend const Rational&

  2. operator*(const Rational& lhs, const Rational& rhs)

  3. {

  4. Rational *result = new Rational(lhs.n*rhs.n, lhs.d*rhs.d);

  5. return *result;//虽然编译器不报错,但是逻辑上是错误的

  6. }

  • 例如我们书写下面的代码,调用了两次operator*,但是我们无法获取在内部申请的result对象的指针,因此就无法释放这两个对象。因此造成内存泄漏
 
  1. Rational w, x, y, z;

  2. w = x*y*z; //相当于operator*(operator*(x,y),z);

错误实现:返回一个静态局部变量

  • 用户还可能定义一个静态局部变量,然后将其引用返回,这样也是错误的
  • 错误原因:我们知道静态局部变量有这样的一个特点,第一次定义之后就保存在程序中(即使函数结束依然存在,知道程序退出才释放),因此当下一次再遇到静态局部变量定义语句的时候不会再执行,因为这个对象已经存在了。那么当我们每回调用operator*函数的时候,每回返回的都是同一个静态局部变量的引用
 
  1. friend const Rational&

  2. operator*(const Rational& lhs, const Rational& rhs)

  3. {

  4. static Rational result; //静态局部变量

  5.  
  6. //result=...; 将lhs乘以rhs,然后将结果保存在result中

  7.  
  8. return *result; //虽然编译器不报错,但是逻辑上是错误的

  9. }

  •  现在我们有下面的代码,那么下面的if语句一定永远为真,因为上面的operator*函数返回的都是同一个对象的引用(即使调用operator*之后result的值可能改变,但是此处比较的是对象,而不是对象的值),因此比较的时候对象时相同的,那么if就为真
 
  1. //针对Rational而写的operator==

  2. bool operator==(const Rational& lhs,const Rational& rhs);

  3.  
  4. int main()

  5. {

  6. Rational a,b,c,d;

  7. //...

  8. if((a*b)==(c*d)){ //此if永远为真,因为operator*返回的都是同一个对象的引用

  9.  
  10. }

  11. else{

  12.  
  13. }

  14. }

二、正确的做法

正确的做法就是如果函数不建议返回对应的引用,那么就一定不能返回对象的引用,应该返回一个对象的副本(虽然会造成额外的构造与析构,但是是值得的)

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

三、总结

  • 绝不要返回指针/引用指向一个局部stack对象,或返回一个引用指向heap-allocated对象,或者返回一个引用/指针指向一个静态局部变量
  • 条款4已经为“在单线程中合理返回引用指向一个静态局部变量”提供了一份设计实例
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值