Effective C++——》 条款21:必须返回对象时,别妄想返回其reference。

当你理解条款21后,很可能出现一种过度使用的情况:只要看到是一个非内置类型,就去使用引用传值。举个书上的例子:

复制代码
1 class Rational
2 {
3 private:
4     int numerator;
5     int denominator;
6 public:
7     Rational():numerator(0), denominator(1){}
8     friend const Rational& operator* (const Rational& r1, const Rational& r2){…}
9 };
复制代码

numerator表示分子,denominator表示分母,这里我们需要把注意力放在operator*上,不是在它的形参上(用的const引用是完全正确的),而是在它的返回值上,这个带引用的返回值是一个巨大的bug。

我们可以设想一下实现:

复制代码
1 friend const Rational& operator* (const Rational& r1, const Rational& r2)
2 {
3         Rational temp;
4         temp.numerator = r1.numerator * r2.numerator;
5         temp.denominator = r1.denominator * r2.denominator;
6         return temp;
7 }
复制代码

这是一种最容易想到的实现方式,在栈上创建对象temp,分子、分母分别相乘后,将之返回。

但仔细再想想,调用函数真的能收到这个temp吗?它是在operator*函数调用时的栈上产生的,当这个函数调用结束后,栈上的temp也会被pop掉,换言之,temp的生命周期仅存在于operator*函数内,离开这个函数,返回的引用将指向一个已经不在的对象!

对此,VS编译器已经给出了warning,如下:

“warning C4172: returning address of local variable or temporary”

千万不能忽略它。

 

那既然栈上创建对象不行,还可以在堆上创建嘛(new出来的都是在堆上创建的),于是我们有:

复制代码
1 friend const Rational& operator* (const Rational& r1, const Rational& r2)
2 {
3     Rational *temp = new Rational();
4     temp->numerator = r1.numerator * r2.numerator;
5     temp->denominator = r1.denominator * r2.denominator;
6     return *temp;
7 }
复制代码

这下VS编译器没有warning了,之前在资源管理那部分说过,new和delete要配对,这里只有new,那delete了?delete肯定不能在这个函数里面做,只能放在外面,这样new和delete实际位于两个不同的模块中了,程序员很容易忘记回收,而且给维护也带来困难,所以这绝对不是一种好的解决方案。书上举了一个例子,比如:

1 Rational w, x, y, z;
2 w = x * y * z;

连乘操作会有两次new的过程,我们很难取得operator*返回的reference背后隐藏的那个指针。

当然,如果把new换成auto_ptr或者是shared_ptr,这种资源泄露的问题就可以避免。

 

栈上创建的临时对象不能传入主调模块,堆上创建的对象就要考虑资源管理的难题,还有其他解决方法吗?

我们还有static对象可以用,static对象位于全局静态区,它的生命周期与这个程序的生命周期是相同的,所以不用担心它会像栈对象那样很快消失掉,也不用担心它会像堆对象那样有资源泄露的危险。可以像这样写:

复制代码
1 friend const Rational& operator* (const Rational& r1, const Rational& r2)
2 {
3     static Rational temp;
4     temp.numerator = r1.numerator * r2.numerator;
5     temp.denominator = r1.denominator * r2.denominator;
6     return temp;
7 }
复制代码

这样写编译器同样不会报错,但考虑一下这样的式子:

1 Rational r1, r2, r3;
2 if(r1 * r2 == r1 * r3){…}

if条件恒为真,这就是静态对象做的!因为所有对象共享这个静态对象,在执行r1*r2时,temp的值为t1,但执行r1*r3之后,temp的值统一都变成t2了。它在类中只有一份,明白这个原因后就不难理解了。

 

既然一个static对象不行,那弄一个static数组?把r1*r2的值放在static数组的一个元素里,而把r1*r3放在static数组的另一个元素里?仔细想想就知道这个想法是多么的天马行空。

 

一个必须返回新对象的正确写法是去掉引用,就这么简单!

复制代码
1 friend const Rational operator* (const Rational& r1, const Rational& r2)
2 {
3     Rational temp;
4     temp.numerator = r1.numerator * r2.numerator;
5     temp.denominator = r1.denominator * r2.denominator;
6     return temp;
7 }
复制代码

该让编译器复制的时候就要放手去复制,就像花钱买东西一样,必须花的终究是要花的。

 

最后总结一下:

绝对不要返回pointer或reference指向一个local stack对象,指向一个heap-allocated对象也不是好方法,更不能指向一个local static对象(数组),该让编译器复制对象的时候,就让它去复制

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
疫情居家办公系统管理系统按照操作主体分为管理员和用户。管理员的功能包括办公设备管理、部门信息管理、字典管理、公告信息管理、请假信息管理、签到信息管理、留言管理、外出报备管理、薪资管理、用户管理、公司资料管理、管理员管理。用户的功能等。该系统采用了MySQL数据库,Java语言,Spring Boot框架等技术进行编程实现。 疫情居家办公系统管理系统可以提高疫情居家办公系统信息管理问题的解决效率,优化疫情居家办公系统信息处理流程,保证疫情居家办公系统信息数据的安全,它是一个非常可靠,非常安全的应用程序。 管理员权限操作的功能包括管理公告,管理疫情居家办公系统信息,包括外出报备管理,培训管理,签到管理,薪资管理等,可以管理公告。 外出报备管理界面,管理员在外出报备管理界面中可以对界面中显示,可以对外出报备信息的外出报备状态进行查看,可以添加新的外出报备信息等。签到管理界面,管理员在签到管理界面中查看签到种类信息,签到描述信息,新增签到信息等。公告管理界面,管理员在公告管理界面中新增公告,可以删除公告。公告类型管理界面,管理员在公告类型管理界面查看公告的工作状态,可以对公告的数据进行导出,可以添加新公告的信息,可以编辑公告信息,删除公告信息
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值