条款21:必须返回对象时,别妄想返回其reference
上篇博客我们讲了pass-by-value效率特别低,除了一些特定情况时,尽量在传递参数的时候使用pass-by-reference-to-const,因此,有的小伙伴就走向了一个极端,坚定追求pass-by-reference纯度,以至于对于返回值也想设置为引用,然鹅物极必反,不用太追求极端的!下面我们来看一些例子,拨开各位小伙伴心中的迷雾!
class Rational{
public:
Rational(int numerator=0,int denominator=1);
...
private:
int n,d;
friend const Rational operator*(const Rational& lhs,const Rational& rhs);//这里请大家不要过分关注于返回值是const ,这是为了避免((a*b)=c)这样的手误,其中a,b,c都是Rational对象
};
这里我们看到程序返回的值为Rational,这样的话不就又要对返回值进行复制,又会消耗内存,那可不嘛?有什么办法呢?
要不要声明为返回值引用呢?
1)在栈空间创建对象
请参看如下代码:
const Rational& operator*(const Rational& lhs,const Rational& rhs){
Rational result(lhs.n*rhs,lhs.d*rhs.d);
return result;
}
这样的话就不用对返回值进行一个复制,效率不就高了吗?抱歉,老铁,这样想可就错啦,注意result是什么,local对象,但operator*函数调用结束后呢?早都释放啦,此时Rational&的地址中什么东西都没啦,只是一个残骸,如果是基本类型等没有析构函数的对象的话,运气好的话还可能存在一段时间,所以这种方法错误吧!
2)在堆空间创建对象
const Rational& operator*(const Rational& lhs,const Rational& rhs){
Rational* result=new Rational(lhs.n*rhs.n,lhs.d*rhs.d);
return *result;
}
这样的话,不就返回的值引用的东西就是head中的吗?没什么问题了吧?这是的确可以返回正确的值,然而呢?我们在堆中创建了对象,什么时候删除呀,老铁,删不掉了吧!尤其是像如下方式调用的时候:
Rational w,x,y,z;
w=x*y*z;
这下连续两次调用了operator*函数,因而两次使用new,也就需要两次使用delete方法,可是没办法delete呀,所以这种方法仍旧错误!
3)声明static变量作为返回引用的值
const Rational& operator*(const Rational& lhs,const Rational& rhs){
static Rational result;
result=...;
return result;
}
这下不就可以解决问题了吗?哈哈哈,老铁,你又栽了,C++的坑多了去了,稍不注意就被坑啦,那么这种方式有什么问题呢?
多线程安全性有问题;
可能计算出的值也有问题,懵了吧,请看如下代码:
#include <iostream>
#include <string>
using namespace std;
int& hel(int t){
static int i = t + 1;
return i;
}
int main(){
int i = 10, j = 100;
cout << hel(i) << " " << hel(j) << endl;
if (hel(i) == hel(j)){
cout << "true" << endl;
}
else{
cout << "false" << endl;
}
return 0;
}
运行结果如下:
可以发现,我们此时的值都相等,为什么呢?因为我们返回的都是static int i的值,而内部的static会被覆盖,所以我们永远看到的都是static的“现值”,因此,两个进行比较肯定都是相等的。
最终,我们才发现原来如果想对operator*这样的函数返回reference,除了采用最极端的方式,才可能实现,例如如下的这种方法:
可能会有比较厉害的小伙伴说我们可以在函数内部声明一个数组,然后返回数组中不同的索引值,可以通过这种复杂的方式最终实现的,那么这样开辟数组空间,大小不能确定,同时复制操作又要浪费时间,构造函数析构函数调用消耗资源,是不是得不偿失呀,大兄弟!
类似于operator*这种函数,我们最推荐的方法是返回对象,而不是拼命返回引用,造成得不偿失或者出错的问题!
const Rational operator*(const Rational& lhs,const Rational& rhs){
return Rational(lhs.n*rhs,lhs.d*rhs.d);
}
总结:
绝不要返回pointer或者reference指向一个local stack对象,或者返回reference指向一个heap-allocated对象,或者返回reference或者pointer指向local static对象,让我们必须在“返回一个reference和返回一个object”之间加以权衡的话,我们需要谨慎决定!