条款18:让接口容易被正确使用,不易被误用
- 在(参数)类型上客户不知道怎么使用的时候,可以导入简单的“外覆”类型来区别参数。也就是,自定义数据类型,使客户明确调用相关的类型,防止误用。
- 尽量让自定义类型的行为和内置类型的行为相同,因为客户会想当然的和使用内置类型一样使用自定义类型,这也就是上面说的让接口容易被正确的使用。
STL
容器的接口十分一致,这也是他们非常容易使用的一个原因。 - 任何接口如果要求客户必须记得做某些事情,那么就有着“不正确的使用”的倾向,因为客户可能会忘记做那件事情。
促进正确使用的办法包括接口的一致性和内置类型的兼容性;阻止误用的办法有建立新类型,限制类型上的操作,束缚对象值以及消除客户的资源管理责任。
条款19:设计 class 犹如设计 type
- 新
type
的对象应该如何被创建和销毁? - 对象的初始化和对象的赋值有什么差别?
- 新
type
的对象如果以值传递,意味着什么? - 什么时新
type
的合法值? - 你的新
type
需要配合某个继承图系吗? - 你的新
type
需要什么样的转换? - 什么样的操作符和函数对此
type
是合法的? - 什么样的标准函数应该被驳回?
- 谁该取用新
type
的成员? - 什么时新
type
的“未声明接口”? - 你的新
type
有多么一般化? - 你真的需要一个新
type
吗?
条款20:宁以 pass-by-reference-to-const 替换 pass-by-value
pass-by-value
在对象的构建和销毁的时候,需要多次调用构造函数和析构函数,是一个昂贵(费时)的操作;pass-by-reference-to-const
效率要高的多:没有任何构造函数或析构函数被调用,因为没有任何新对象被创建。reference
能够消除对象的创建销毁过程和切割问题(参数是基类型,如果不使用引用,那么会引起切割),const
的作用是让函数不能修改对象的值。
如果窥视C++
编译器的底层,你会发现,引用往往是以指针的形式实现出来,因此,传递引用往往意味着真正传递的是指针。如果对象属于内置类型,pass-by-value
往往比pass-by-reference
效率高一些。
条款21:必须返回对象时,别妄想返回其reference
任何时候看到一个reference
声明式,都应该立刻问自己,它的另一个名称是什么?因为它一定是某物的另一个名称。
函数创建新对象的途径有两个:heap
(堆) 和 stack
(栈)
中。如果定义一个local
对象,那就是在栈空间创建。当函数返回的时候,栈空间将会释放,但是返回的却是析构了的对象的引用,接下来,问题多多的。你很聪明,你会想到在堆中创建对象,这样函数返回也不会析构这个对象,这确实是可以的,如此,将会产生另一个问题:谁该对被new
出来的对象实施delete
?
即使调用者诚实谨慎,当发生下面情况的时候,也就不能阻止内存泄露:
Rational w, x, y, z;
w = x*y*z;
同意个语句调用两次函数(*
),两次使用new
,释放两次才可以。但却没有合适的方法让他们取得函数返回的引用,也就不能实施释放,这绝对会导致资源泄露。
你可能还很聪明,让对象定义为static
,这样自我感觉还不错,但是,问题还是来了。前一次计算的结果会累计,并影响下一次的计算。这显然也不是我们希望发生的。
解决的方法也不难想到:一个必须返回对象的函数就让那个函数返回新对象吧。当然,这会引起对象构造和析构的成本,然而,长远来看,那只是为了获取正确行为而付出的小小代价。即使需要优化,那也是编译器作者的事情。
条款22:将成员变量声明为 private
- 如果成员变量不是
public
,那么客户访问变量的唯一方式就是使用成员函数,也就是说,对成员变量的所有访问都是通过括号的形式访问的,也达到了接口一致性的要求。 - 可以使用函数让你对成员变量的处理有更精确的控制。可以实现只读或者变量正确性的控制。
- 封装。如果使用函数访问成员变量,日后可以修改某个计算替换这个成员变量,而
class
客户一点都不会知道类的内部发生了变化。 - 举个例子:我们要计算某汽车多次测试的平均速度,通过一个函数返回这个值,两种实现方式:用一个变量存储这个均值,查询的时候只要返回这个值即可;每次函数调用的时候重新计算均值。
上述第一种做法 - 利:查询速度快,弊:大量空间存储任意时刻的速度均值;第二种做法:查询速度慢,但是需要的内存空间小。
哪一个方案更好呢?情况不同,答案不同。所以,将成员变量隐藏在函数接口的后面,可以为“所有可能的实现”提供弹性。
Protected
成员变量和Public
一样缺乏封装性,因为在这两种情况下,如果成员变量被改变,都会有不可预知的大量代码受到破坏。
条款23:宁以 non-member
non-frind
替换 member
函数
一个观点:成员函数的封装性比非成员函数的封装性要低。
越多的东西被封装,越少人可以看见它;越少人可以看见它,我们就有越大的弹性去改变它 --- 越多东西被封装,我们改变那些东西的能力就越大。 --- 封装:使我们能够改变事物而只影响有限客户。
我们量化一下:使用能够访问数据的函数数量作为封装性的一种粗糙量化。越多函数能够访问它,数据的封装性就越低,反之亦然。
能够访问私有数据成员的函数只有成员函数和友元函数,如果在member
函数,non-member
函数和
non-friendnon-member
函数之间做选择,non-member
函数,因为它不增加“能够访问私有数据的函数”数量。 对吧,因为它的封装性更高,这也是我们想要的。
non-friend
我们将上述函数称之为“便利函数”,因为它是通过使用共有成员函数完成一些成组或者组合的操作。将所有“便利函数”放在多个头文件中但隶属同一个命名空间,意味着客户可以轻松扩展这一组便利函数。 --- 就像C++
标准库所做的那样。