Item 21:需要返回对象时,不要返回引用

不要返回空的引用或指针

一个典型的场景如下:

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

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 是一个局部对象,而局部对象在函数退出时被销毁。这样的调用将会产生未定义的行为。

如果在堆上创建:

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);
}

总结

  • 绝不要返回一个局部栈对象的指针或引用。
  • 绝不要返回一个被分配的堆对象的引用。
  • 如果存在需要一个以上这样的对象的可能性时,绝不要返回一个局部 static 对象的指针或引用。
posted @ 2020-02-08 17:23  刘-皇叔  阅读(106)  评论(0编辑  收藏  举报