条款21: 必须返回对象时,不要强行返回对象的reference
总结:
绝不要返回一个local栈对象的指针或引用;绝不要返回一个被分配的堆对象的引用;绝不要返回一个静态局部对象(为了它,有可能同时需要多个这样的对象的指针或引用)。
条款4中给出了“在单线程环境中合理返回局部静态对象的引用”。
注意:利用指针返回一个被分配的堆对象是可以的。本条款讨论的是必须返回一个对象,所以返回一个对象的指针不包含在本条款内。那可能说,能返回对象的指针,为什么还需要返回一个对象呢?因为有的时候真的是需要返回一个对象,不能返回对象的指针。
提出问题
因为对象之间值传递是有效能的损耗的,所以引用传递的方式似乎更好,但是要避免一个错误:传递不存在的对象的引用。考虑一个用以表现有理数的类,包含一个函数计算两个有理数的乘积:
class Rational {
public:
Rational(int numerator = 0, int denominator = 1);
...
private:
int n, d; // 分子与分母
friend const Rational operator*(const Rational& lhs, const Rational& rhs);
};
这个版本operator* 以传值方式返回它的结果,需要付出对象的构造和析构成本。如果你能用返回一个引用来代替,就不需付出代价。但是,请记住一个引用仅仅是一个别名,一个实际存在的对象的名字。无论何时只要你看到一个引用的声明,应该立刻问自己它是什么东西的别名,因为它必定是某物的别名。以上述operator*为例,如果函数返回一个引用,它必然返回某个既有的而且包含两个对象相乘产物的Rational对象引用。
当然没有什么理由期望这样一个对象在调用operator*之前就存在。也就是说,如果你有
Rational a(1, 2); // a = 1/2
Rational b(3, 5); // b = 3/5
Rational c = a * b; // c should be 3/10
期望原本就存在一个值为3/10的有理数对象并不合理。如果operator*返回一个reference指向如此数值,它必须自己创建那个Rational对象。
函数创建新对象仅有三种方法:在栈或在堆上或者在static的静态区域。
在栈空间上创建对象
如果定义一个local变量,就是在栈空间创建对象:
const Rational& operator*(constRational& lhs, const Rational& rhs)
{
Rational result(lhs.n * rhs.n, lhs.d * rhs.d);
return result;
} //糟糕的代码!
本来目的是避免调用构造函数,而result却必须像任何对象一样由构造函数构造而来。这个函数返回一个指向result的引用,但是result是一个局部对象,在函数退出时被销毁了。因此这个operator*的版本不会返回指向一个Rational的引用,它返回指向一个“从前的”Rational,一个旧时的Rational,一个曾经被当做Rational但如今已经成空、发臭、败坏的残骸,因为它已经被销毁。任何调用者甚至只是对此函数的返回值做任何一点点运用,就立刻进入了未定义行为的领地。这是事实,任何返回一个指向局部变量引用(或指针)的函数都是错误的。
在堆空间上创建对象
考虑一下在堆上构造一个对象并返回指向它的引用的可能性。基于堆的对象通过使用new创建:
const Rational& operator*(const Rational& lhs, const Rational& rhs)
{
Rational *result = new Rational(lhs.n * rhs.n, lhs.d * rhs.d);
return *result;
} //更糟的写法!
谁该对用new创建出来的对象实施delete?
Rational w, x, y, z;
w = x * y * z; // 与operator*(operator*(x, y), z)相同
这里,在同一个语句中有两个operator*的调用,因此new被使用了两次,这两次都需要使用 delete来销毁。但是operator*的客户没有合理的办法进行那些调用,因为他们没有合理的办法取得隐藏在通过调用operator*返回的引用后面的指针。这绝对导致资源泄漏。
在静态数据区创建static对象
无论是在栈还是在堆上的方法,为了从operator*返回的每一个 result,我们都不得不容忍一次构造函数的调用,而我们最初的目标是避免这样的构造函数调用。我们可以继续考虑基于 operator*返回一个指向staticRational对象引用的实现,而这个static Rational对象定义在函数内部:
const Rational& operator*(constRational& 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
{当乘积不等时,做适当的相应动作}
除了和所有使用static对象的设计一样可能引起的线程安全(thread-safety)的混乱,上面不管 a,b,c,d 的值是什么,表达式 ((a*b) == (c*d)) 总是等于 true!如果代码重写为功能完全等价的另一种形式,很容易了解出了什么意外:
if (operator==(operator*(a, b), operator*(c, d)))
在operator==被调用前,已有两个起作用的operator*调用,每一个都返回指向 operator*内部的staticRational对象的引用。两次operator*调用的确各自改变了staticRational对象值,但由于它们返回的都是reference,因此调用端看到的永远是static Rational对象的“现值”。因此operator==被要求拿operator*的static Rational对象值和operator*的static Rational对象值比较,如果不相等才奇怪。
注意:本条款说的是不能利用static对象作为一个函数的返回值,这样会返回同一个静态对象。更不能为了不返回同一个静态对象,而开辟一个静态对象的数组,因为这样的代价更大。但是如果这种返回同一个静态对象的设计恰恰是我需要的单例模式,那就可以了,比如说条款4。
就让它返回一个新对象
一个必须返回新对象的函数的正确方法:让那个函数返回一个新对象。对于Rational的 operator*,这就意味着下面这些代码或在本质上与其等价的代码:
inline const Rational operator*(const Rational& lhs, const Rational& rhs)
{return Rational(lhs.n * rhs.n, lhs.d * rhs.d);}
当然,你可能付出了构造和析构operator*的返回值的成本,但是从长远看,这只是为正确行为付出的很小代价。但万一代价很恐怖,你可以允许编译器施行最优化,用以改善出码的效率却不改变其可观察的行为。因此某些情况下operator*返回值的构造和析构可被安全的消除。如果编译器运用这一事实(它们也往往如此),程序将保持应有行为,而执行起来又比预期的更快。
总结:如果需要在返回一个引用和返回一个对象之间做决定,你的工作就是让那个选择能提供正确的行为,不能为了效能产生错误的行为。并不是说什么时候都不能返回对象的引用,下面的赋值拷贝构造函数返回对象的引用就是正确的。
CMyString& operator =(const CMyString& str);
条款21: 必须返回对象时,不要强行返回对象的reference的更多相关文章
- Spring 中初始化一个Bean对象时依赖其他Bean对象空指针异常
1. Bean依赖关系 一个配置类的Bean,一个实例Bean: 实例Bean初始化时需要依赖配置类的Bean: 1.1 配置类Bean @ConfigurationProperties(prefix ...
- EC笔记:第4部分:21、必须返回对象时,别返回引用
使用应用可以大幅减少构造函数与析构函数的调用次数,但是引用不可以滥用. 如下: struct St { int a; }; St &func(){ St t; return t; } 在返回t ...
- 1.部分(苹果)移动端的cookie不支持中文字符,2.从json字符串变为json对象时,只支持对象数组
1.移动端的cookie不支持中文字符.可以用编码,解码的方式解决. 2.json字符串变成相应 的,json对象数组字符串.就这样 3.不同客户端(移动端.电脑)的请求,在C#服务端的取时间的格式竟 ...
- 条款21: 尽可能使用const
对指针来说,可以指定指针本身为const,也可以指定指针所指的数据为const,或二者同时指定为const,还有,两者都不指定为const: char *p = "hello"; ...
- jsp九个内置对象、四个域对象及Servlet的三大域对象
一,什么是内置对象? 在jsp开发中会频繁使用到一些对象,如ServletContext HttpSession PageContext等.如果每次我们在jsp页面中需要使用这些对象都要自己亲自动手创 ...
- 条款21:必须返回对象object时,不要返回其引用reference
如下为一个有理数类,其中包含一个用来计算乘积的函数: #include <iostream> using namespace std; class Rational { public: R ...
- 【21】必须返回对象时,别妄想返回器reference
1.考虑有理数Rational,有个友元操作符*,返回Rational对象.返回对象,导致临时对象的构造,析构.效率低,因此会想返回方法内局部对象的引用,这种方法不可行.为什么? 2.调用方法是在st ...
- 条款21:必须返回对象的时候,不要妄想使其返回reference
//先看看下面这个例子 class Rational{ public: Rational(int num, int denu) :numirator(num), denumirator(denu); ...
- 解决springmvc在单纯返回一个字符串对象时所出现的乱码情况(极速版)
使用springmvc框架开发了这么长时间,之前都是直接返回jsp页面,乱码情况都是通过配置和手动编解码来解决,但是今天突然返回一段单纯的字符串时,发现中文乱码情况解决不了了,下面就给各位分享一下如何 ...
随机推荐
- Android中自定义属性的使用
做Android布局是件很享受的事,这得益于他良好的xml方式.使用xml可以快速有效的为软件定义界面.可是有时候我们总感觉官方定义的一些基本组件不够用,自定义组件就不可避免了.那么如何才能做到像官方 ...
- iPhone 5s网络钓鱼邮件,和苹果发布会同步亮相
正如预期的一样,网络犯罪分子会利用Apple最新发表的iPhone 5s消息,几乎在苹果的新产品发表会同时,这个网络钓鱼(Phishing)信件开始流传.此次,趋势科技病毒防治中心 Trend Lab ...
- HDFS 2中Namenode启动时WebUI的变化
在HDFS1中NameNode启动顺序是这样的: 1. 读取Fsimage文件 2. 读取edit logs文件,逐行执行里面的操作 3. 写checkpoint,生成新的Fsimage(老的Fs ...
- Mysql文件太大导入失败解决办法总结
Mysql文件太大导入失败解决办法总结 在使用phpmyadmin导入数据库的时候可能会碰到由于数据库文件太大而无法导入的问题! 英文提示如下:File exceeds the maximum all ...
- hdu2554-N对数的排列问题
http://acm.hdu.edu.cn/showproblem.php?pid=2554 假设所有的2n个数据的位置分别从1~2n标号. 现在假设其中第ai个数据(双胞胎),和bi.那么他们的位置 ...
- [C# 基础知识系列]专题六:泛型基础篇——为什么引入泛型
引言: 前面专题主要介绍了C#1中的2个核心特性——委托和事件,然而在C# 2.0中又引入一个很重要的特性,它就是泛型,大家在平常的操作中肯定会经常碰到并使用它,如果你对于它的一些相关特性还不是很了解 ...
- java中如何获取系统时间
需要引入的包有: import java.util.Date; 此为获取当前系统时间,合适为“1991-01-01” String now = ""; SimpleDateF ...
- Spring--依赖注入
Spring 能有效地组织J2EE应用各层的对象.不管是控 制层的Action对象,还是业务层的Service对象,还是持久层的DAO对象,都可在Spring的 管理下有机地协调.运行.Spring将 ...
- jbpmAPI-6
第六章流程. 6.1. What is BPMN 2.0 业务流程模型和符号(BPMN)2.0规范是OMG规范,不仅定义了一个标准的业务流程的图形化表述(如BPMN 1. x),但现在还包括执行语义定 ...
- java 读文件 解析
[Java]读取文件方法大全 1.按字节读取文件内容2.按字符读取文件内容3.按行读取文件内容 4.随机读取文件内容 public class ReadFromFile { /** ...