条款21:必须返回对象时,别妄想返回其reference
Don’t try to return a reference when you must return an object.
在上一篇中,我们分别接受了stack和heap两种方式的构造对象,然而,无论是on-the-stack,还是on-the-heap的做法,都因为对operator* 返回的结果调用构造函数而产生了问题,而最开始的目标就是避免如此的构造函调用动作!
于是,另一个想法则是基于:
- 让operator* 返回的reference指向一个被定义于函数内部的static Rational对象。
const Rational& operator* ( const Rational& lhs,
const Rational& rhs)
{
static Rational result; //还是很糟糕!定义static对象,该函数将返回它的reference
result = ... ; //将lhs乘以rhs,并将结果置于result之内
return result;
}
上面的代码之所以糟糕,是因为如果对于以下的使用代码:
bool operator==(const Rational& lhs,
const Rational& rhs); //一个针对Rational而写的operator==
Rational a, b, c, d;
///
if ((a * b) == (c * d))
{
乘积相等所执行的动作
}
else
{
乘积不等所执行的动作
}
出现的问题是:
- 无论a, b, c, d的值是多少,表达式((a * b) == (c * d))总是被判定为True!
让我们将上述的if判断语句写成等价的函数形式:
if (operator==(operator*(a, b), operator*(c, d))
上面这句代码,可以看到在operator==被调用之前,已经有两个operator* 被调用了,并且每一个都返回reference指向operator* 内部定义的static Rational对象。
因此,operator==被要求将“operator* 内的static Rational对象值” 拿来和“operator* 内的static Rational对象值” 进行比较,这自然就是相等了。
值得注意的是,两次的operator*的调用确实是改变了static Rational对象值,但是由于他们都是返回reference,因此调用段看到的永远是static Rational对象的“现值”。
于是,一个“必须返回新对象”的函数的正确写法是:
- 直接让这个函数返回一个新对象。
incline const Rational operator* ( const Rational& lhs, const Raitonal& rhs)
{
return Rational(lhs.n * rhs.n, lhs.d * rhs.n);
}
当然了,operator*返回值的构造成本和析构成本是必须支出的,但是这只是获得正确行为的小小代价。
总之,
- 当我们必须在“返回一个reference和返回一个object”之间进行选择时,选择行为正确的那一个。
最后: