总结:

绝不要返回一个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的更多相关文章

  1. Spring 中初始化一个Bean对象时依赖其他Bean对象空指针异常

    1. Bean依赖关系 一个配置类的Bean,一个实例Bean: 实例Bean初始化时需要依赖配置类的Bean: 1.1 配置类Bean @ConfigurationProperties(prefix ...

  2. EC笔记:第4部分:21、必须返回对象时,别返回引用

    使用应用可以大幅减少构造函数与析构函数的调用次数,但是引用不可以滥用. 如下: struct St { int a; }; St &func(){ St t; return t; } 在返回t ...

  3. 1.部分(苹果)移动端的cookie不支持中文字符,2.从json字符串变为json对象时,只支持对象数组

    1.移动端的cookie不支持中文字符.可以用编码,解码的方式解决. 2.json字符串变成相应 的,json对象数组字符串.就这样 3.不同客户端(移动端.电脑)的请求,在C#服务端的取时间的格式竟 ...

  4. 条款21: 尽可能使用const

    对指针来说,可以指定指针本身为const,也可以指定指针所指的数据为const,或二者同时指定为const,还有,两者都不指定为const: char *p = "hello"; ...

  5. jsp九个内置对象、四个域对象及Servlet的三大域对象

    一,什么是内置对象? 在jsp开发中会频繁使用到一些对象,如ServletContext HttpSession PageContext等.如果每次我们在jsp页面中需要使用这些对象都要自己亲自动手创 ...

  6. 条款21:必须返回对象object时,不要返回其引用reference

    如下为一个有理数类,其中包含一个用来计算乘积的函数: #include <iostream> using namespace std; class Rational { public: R ...

  7. 【21】必须返回对象时,别妄想返回器reference

    1.考虑有理数Rational,有个友元操作符*,返回Rational对象.返回对象,导致临时对象的构造,析构.效率低,因此会想返回方法内局部对象的引用,这种方法不可行.为什么? 2.调用方法是在st ...

  8. 条款21:必须返回对象的时候,不要妄想使其返回reference

    //先看看下面这个例子 class Rational{ public: Rational(int num, int denu) :numirator(num), denumirator(denu); ...

  9. 解决springmvc在单纯返回一个字符串对象时所出现的乱码情况(极速版)

    使用springmvc框架开发了这么长时间,之前都是直接返回jsp页面,乱码情况都是通过配置和手动编解码来解决,但是今天突然返回一段单纯的字符串时,发现中文乱码情况解决不了了,下面就给各位分享一下如何 ...

随机推荐

  1. 网络上USB后面跟AF或AM接口的意思

    AM to micro 5pin或AF to micro 5pin 这里的AM或AF是Type a male和Type a female的简写 Type a male:A型男士 Type a fema ...

  2. Qt监测光驱变化(使用WM_DEVICECHANGE)

    xxx.h protected: bool winEvent(MSG *msg,long * result); xxx.cpp bool CBlurayTranscoderDlg::winEvent( ...

  3. xmu1214: 购物

    1214: 购物 Time Limit: 500 MS  Memory Limit: 64 MBSubmit: 10  Solved: 5[Submit][Status][Web Board] Des ...

  4. Android应用开发基础篇(14)-----自定义标题栏

    一.概述 每一个应用程序默认的标题栏(注意与状态栏的区别)只有一行文字(新建工程时的名字),而且颜色.大小等都是固定的,给人的感觉比较单调.但当程序需要美化的时候,那么修改标题栏是就是其中一项内容,虽 ...

  5. java String.Format详解

    JDK1.5中,String类新增了一个很有用的静态方法String.format(): format(Locale l, String format, Object... args) 使用指定的语言 ...

  6. xcode initWithCoder\awakeFromNib\layoutSubviews

    控件通过xib,storyboard创建,初始化设置一定会调用initWithCoder awakeFromNib 加载完毕的时候肯定会调用 layoutSubviews 布局子控件 位置和尺寸 利用 ...

  7. javascript操作HTML元素

    操作 HTML 元素 如需从 JavaScript 访问某个 HTML 元素,您可以使用 document.getElementById(id) 方法. 请使用 "id" 属性来标 ...

  8. 新浪微博 2.4sdk 一闪而过

    解决方法,保持 ios应用中的 build id和 开放平台中填写的一致

  9. HTML技术简介

    1.DHTML:"Dynamic HTML"动态HTML技术的简称.DHTML并不是一项新技术,而是HTML,CSS,JavaScript技术组合的术语.DHTML背后的含义是: ...

  10. ubuntu16.04安装kde桌面出错: /var/cache/apt/archives/kde-config-telepathy-accounts_4%3a15.12.3-0ubuntu1_amd64.deb

    出错提示: 正在读取软件包列表... 完成 正在分析软件包的依赖关系树 正在读取状态信息... 完成 kubuntu-desktop 已经是最新版 (1.338). 您可能需要运行“apt-get - ...