Effective C++ 读书笔记 Item21 需要返回对象时,不要返回引用

 我们发现,在C++中,有些成员函数返回的是对象,而有些函数返回的又是引用。

返回对象和返回引用的最主要的区别就是函数原型和函数头。

Car run(const Car &)     //返回对象

Car & run(const Car &)   //返回引用

Item 20中提到,多数情况下传引用比传值更好。追求这一点是好的,但千万别返回空的引用或指针。 一个典型的场景如下:

class Rational{
  int n, d;
public:
  Raitonal(int numerator=0, int denominator=1);
};
 

// 返回值为什么是const请参考Item 3

friend const Rational operator*(const Rational& lhs, const Rational& rhs);
 
Rational a, b;
Rational c = a*b;

注意operator*返回的是Rational实例,a*b时便会调用operator*(), 返回值被拷贝后用来初始化c。这个过程涉及到多个构造和析构过程:

函数调用结束前,返回值被拷贝,调用拷贝构造函数
函数调用结束后,返回值被析构
c被初始化,调用拷贝构造函数
c被初始化后,返回值的副本被析构
我们能否通过传递引用的方式来避免这些函数调用?这要求在函数中创建那个要被返回给调用者的对象,而函数只有两种办法来创建对象:在栈中创建、或者在堆中创建。在栈中创建显然是错误的:

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

客户得到的result永远是空。因为引用只是一个名称,当函数调用结束后result即被销毁。 它返回的是一个ex-result的引用。那么在堆中创建会是怎样的结果?

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

问题又来了,既然是new的对象,那么谁来delete呢?比如下面的客户代码:

Rational w, x, y, z;
w = x*y*z;

上面这样合理的代码都会导致内存泄露,那么operator*的实现显然不够合理。此时你可能想到用静态变量来存储返回值,也可以避免返回值被再次构造。但静态变量首先便面临着线程安全问题,除此之外当客户需要不止一个的返回值同时存在时也会产生问题:

if((a*b) == (c*d)){
  // ...
}

如果operator*的返回值是静态变量,那么上述条件判断恒成立,因为等号两边是同一个对象嘛。如果你又想到用一个静态变量的数组来存储返回值,那么我便无力吐槽了。。。

挣扎了这许多,我们还是返回一个对象吧:

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

事实上拷贝构造返回值带来的代价没那么高,C++标准允许编译器做出一些客户不可察觉(without changing observable behavior)的优化。在很多情况下,返回值并未被析构和拷贝构造。

永远不要返回局部对象的引用或指针或堆空间的指针,如果客户需要多个返回对象时也不能是局部静态对象的指针或引用。 Item 4:确保变量的初始化指出,对于单例模式,返回局部静态对象的引用也是合理的。
 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值