如果你传递一些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对象提供了一份设计实例。