Skip to content

Latest commit

 

History

History
98 lines (72 loc) · 3.29 KB

File metadata and controls

98 lines (72 loc) · 3.29 KB

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

如果你传递一些reference指向其实不存在的对象,这可不是件好事。

考虑一个用以表现有理数的class,内含一个函数用来计算两个有理数的乘积:

class Rational {
public:
  Rational(int numerator = 0, int denominator = 1);
  ...
private:
  int n, d;
friend const Rational operator*(const Rational& lsh,
  const Rational& rhs);
};

这个版本的 operator* 以 by value方式返回其结果(一个对象)。这会发生拷贝,付出太多代价。

如果可以改成传递reference,就不需要付出代价。但是 operator* 必须自己创建一个对象,指向计算的结果。

函数创建新对象的途径有二:在stack或heap空间创建。

如果定义一个local变量,就是在stack创建的对象。

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

这个函数返回一个引用指向local对象result,而这个Rational对象已经被销毁了。这样会造成未定义行为。

于是,我们考虑在heap内构造一个对象,并返回reference指向它。

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

但还是付出了调用一个构造函数的代价。但此外你现在又有了另一个问题:谁该对new的对象实施delete?

即使调用者记得delete,但它们还是不能够在这样的用法下阻止内存泄露:

Rational w, x, y, z;
w = x * y * z;  // operator*(operator*(x, y), z)

这里一个语句调用了两次 operator*,因而两次使用new,也就需要两次delete。然而没有合理的办法使用delete,这导致内存泄漏。

如果让 operator* 返回的reference指向一个被定义于函数内的static Rational对象:

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

static也会立刻造成我们对多线程安全性的疑虑。不过更深层的瑕疵,考虑以下完全合理的用户代码:

Rational a, b, c, d;
...
if ((a * b) == (c * d)) {
  ...
} else {
  ...
}

表达式 (a * b) == (c * d) 总是返回true,不论a,b,c,d是什么。

代码重写为等价形式,可以看到任何时候的运算返回值总是指向同一个静态对象。

if (operator==(operator*(a, b), operator*(c, d)))

所以,让 operator* 这样的函数返回reference,只是浪费时间而已。一个必须返回新对象的函数正确写法是:让它返回一个新对象,

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

别担心拷贝方式的时间消耗,编译器会做出优化的。

请记住

  • 绝不要返回指针或引用指向一个local stack对象,或返回reference指向一个heap对象,或返回一个指针或引用指向一个local static对象而有可能需要多个这样的对象。条款4已经为在单线程环境中合理返回引用指向一个local static对象提供了一份设计实例。