考虑一个表示有理数的类,其中包含一个计算两个有理数相乘的函数:
class Rational {
public:
Rational(int numerator = 0, int denominator = 1)
:n{ numerator }, d{ denominator }
{
}
private:
int n, d; // 分子和分母
friend const Rational& operator*(const Rational& , const Rational& );
};
const Rational& operator*(const Rational& lhs, const Rational& rhs)
{
Rational result(lhs.n * rhs.n, lhs.d * rhs.d);
return result;
}Rational a(1, 2); // a = 1/2
Rational b(3, 5); // b = 3/5
Rational c = a * b; // c 应该是 3/10
这里还是发生了构造和析构,更糟糕的是引用的对象被析构了。在现在的编译器里面这个结果有可能是正确的。
让我们考虑在堆上构造一个对象并返回它的引用的可能性。
const Rational& operator*(const Rational& lhs, // 警告! 糟糕的代码!
const Rational& rhs)
{
Rational* result = new Rational(lhs.n * rhs.n, lhs.d * rhs.d);
return *result;
}
Rational w, x, y, z;
w = x * y * z; // 等同于operator*(operator*(x, y), z)
使用了new构造对象,没有地方可以进行调用delete。
也许你会想到一个基于operator*的实现,它返回一个定义在函数内部的静态Rational对象的引用。
const Rational& operator*(const Rational& lhs, // 警告!还是烂代码!
const Rational& rhs)
{
static Rational result; // 静态对象
result = ...; // 计算
return result;
}
bool operator==(const Rational& lhs, const Rational& rhs);
Rational a, b, c, d;
...
if ((a * b) == (c * d)) {
do … ;
} else {
do … ;
}
结果也是错误的,由于操作的都是同一块内存,所以执行后的结果都是最后一次执行的结果。
正确的做法是编写必须返回新对象的函数的正确方式是让该函数返回一个新对象:
inline const Rational operator*(const Rational& lhs, const Rational& rhs)
{
return Rational(lhs.n * rhs.n, lhs.d * rhs.d);
}
绝不要返回指针或引用指向一个local stack对象,或返回引用指向一个heap-allocated对象,或返回指针或对象指向一个local static对象而有可能同时需要多个这样的对象。条款4已经为“在单线程环境中合理返回引用指向一个local static对象”提供了一份设计示例。