条款二十一:不要总想着返回对象的引用而不返回对象本身
我们上一条了解到pass-by-value
会带来效率上的问题,但不要什么都pass-by-reference
。特别是当一些reference
会指向不存在的对象的时候。
考虑下面一个例子:
1 | class Rational{ |
所以,像operator*
这样必须返回一个对象的函数就让他返回一个新对象。
- 不要返回指向local变量的指针或者引用。单线程中,只有一个local static对象的情况是例外(单例模式)。
条款二十二:将成员变量声明为private
首先是为了语法一致性,如果成员变量不是public,那么用户只能访问成员函数,用户可以很容易地记住使用方法。
第二是可以堆成员变量的访问进行有效的控制。
如:
1 | class AccessLevels{ |
第三,为了更好的封装性,将成员变量生命为private可以对用户隐藏处理他们的细节,防止后续对这些处理过程改变时引起的大量的代码重写。
第四,protected不必public封装性强,因为如果基类的protected成员改变时,其派生类的所有相关函数都要进行修改。
- 将成员变量声明为private,这可以赋予用户访问数据的一致性,可细粒度地划分访问控制权限,并在修改class时堆用户造成较小的麻烦。
- protected封装性并不比public强。
条款二十三:以非成员,非友元替换成员函数
举的是浏览器类的例子:
1 | class WebBrowser{ |
从题目可以知道,肯定是非成员函数的版本号(笑)。那么为什么呢?
这一条款只适用于在 成员函数 和 非成员函数,非友元函数之间进行选择的时候。
首先是封装性,越少的函数可以直接访问数据,封装性就越强,非成员函数形式并不能直接访问数据,可以知道分成原函数的版本封装性更强。
还有一个要注意的是clearBrowser
可以是其他类的成员函数。
C++中比较普遍的做法是让clearBrowser
称为一个non-member
函数并且位于和WebBrowser
相同的命名空间内。
1 | namespace WebBrowserStuff{ |
namespace
是可以跨文件的,所以我们可以把对一种数据的操作都放入一个头文件中,并且以WebBrowserStuff
这一命名空间将其包裹。
这样的写法类似于使用标准库时,使用vector
时写上#include <vector>
用到哪些功能,就包含那个头文件,降低了编译依赖。同时,用户还可以扩展如cleraBrowser
这样的函数,灵活地实现想要的功能。
- 可以实现相同的功能时,用非成员函数,非友元函数替换成员函数,可以增加封装性,可扩展性。
条款二十四:若函数的所有参数都需要类型转换,要使用non-member函数
需要class支持隐式类型转换时:如定义了一个有理数类,允许其与int类型转换是一个合理的操作。
1 | class Rational{ |
当你想要实现一些算术操作时,首先会想到以成员函数来实现他们。
1 | class Rational{ |
这不符合我们乘法应该满足交换律的认知。会出现错误的原因是2*oneHalf
可以看成operator*(2, oneHalf)
。但是并没有这样接收一个int
类型和一个Rational
为参数的乘法操作符。调用成功的是因为将2默认转换成了Rational
。而当我们把operator*
定义为一个非成员函数的时候,就可以解决这个问题。
1 | const Rational operator*(const Rational& lhs, const Rational& rhs) |
如果一个与某个class相关的函数不能成为成员函数,首先考虑的是用非成员函数解决它,而不是友元函数。
- 如果某个函数的所有参数都要进行类型转换,那么这个函数必须是一个非成员函数。
条款二十五:写出一个不抛出异常的swap函数
swap
是STL的一部分,缺省情况下swap
的定义是这样的:
1 | namespace std{ |
只要T支持复制构造函数和赋值运算符swap
就能顺利完成。而在一些情况下我们并不需要这样全盘赋值来实现swap
。举一个典型的例子是pImpl设计模式。以指针指向一个对象,指向的对象内有真正的数据。
1 | class WidgetImpl{ |
这时如果我们还调用std::swap
的话,这个函数会赋值三个Widget
和三个WidgetImpl
对象,这大大影响了效率,我们完全可以通过直接交换pImpl
指针来完成swap
的工作。
正确的做法:在Widget
内声明一个swap
的public
成员函数来做真正的置换工作,然后实现std::swap
的特化版本来调用这个函数。
1 | class Widget{ |
如果上面提到的Widget
和WidgetImpl
是class template
而非class
,我们如果想对其实现swap
函数,应该采用类似的做法,同时也体现了要合理运用命名空间这一点:
1 | namespace WidgetStuff{ |
- 自己实现效率更高
swap
的时候,提供一个成员函数版本的swap
并提供一个非成员函数的swap
来调用前者,对于非模板类,要特化std::swap
- 调用
swap
时应针对std::swap
使用using
声明式,然后不带任何命令空间修饰地调用swap
- 不要在
std
内加入对std
而言全新的东西