首页 > 代码库 > Effective C++ (笔记) : 条款18 -- 条款23

Effective C++ (笔记) : 条款18 -- 条款23

条款18:让接口容易被正确使用,不易被误用

  1. 在(参数)类型上客户不知道怎么使用的时候,可以导入简单的“外覆”类型来区别参数。也就是,自定义数据类型,使客户明确调用相关的类型,防止误用。

  2. 尽量让自定义类型的行为和内置类型的行为相同,因为客户会想当然的和使用内置类型一样使用自定义类型,这也就是上面说的让接口容易被正确的使用。STL容器的接口十分一致,这也是他们非常容易使用的一个原因。

  3. 任何接口如果要求客户必须记得做某些事情,那么就有着“不正确的使用”的倾向,因为客户可能会忘记做那件事情。

促进正确使用的办法包括接口的一致性和内置类型的兼容性;阻止误用的办法有建立新类型,限制类型上的操作,束缚对象值以及消除客户的资源管理责任

条款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

  1. 如果成员变量不是public,那么客户访问变量的唯一方式就是使用成员函数,也就是说,对成员变量的所有访问都是通过括号的形式访问的,也达到了接口一致性的要求。
  2. 可以使用函数让你对成员变量的处理有更精确的控制。可以实现只读或者变量正确性的控制。
  3. 封装。如果使用函数访问成员变量,日后可以修改某个计算替换这个成员变量,而class客户一点都不会知道类的内部发生了变化。 - 举个例子:我们要计算某汽车多次测试的平均速度,通过一个函数返回这个值,两种实现方式:用一个变量存储这个均值,查询的时候只要返回这个值即可;每次函数调用的时候重新计算均值。

上述第一种做法 - 利:查询速度快,弊:大量空间存储任意时刻的速度均值;第二种做法:查询速度慢,但是需要的内存空间小。

哪一个方案更好呢?情况不同,答案不同。所以,将成员变量隐藏在函数接口的后面,可以为“所有可能的实现”提供弹性。

Protected成员变量和Public一样缺乏封装性,因为在这两种情况下,如果成员变量被改变,都会有不可预知的大量代码受到破坏。

条款23:宁以 non-member non-frind替换 member 函数

一个观点:成员函数的封装性比非成员函数的封装性要低

越多的东西被封装,越少人可以看见它;越少人可以看见它,我们就有越大的弹性去改变它 --- 越多东西被封装,我们改变那些东西的能力就越大。 --- 封装:使我们能够改变事物而只影响有限客户。

我们量化一下:使用能够访问数据的函数数量作为封装性的一种粗糙量化。越多函数能够访问它,数据的封装性就越低,反之亦然。

能够访问私有数据成员的函数只有成员函数和友元函数,如果在member函数,non-member non-friend函数和non-member函数之间做选择,non-member non-friend函数,因为它不增加“能够访问私有数据的函数”数量。 对吧,因为它的封装性更高,这也是我们想要的。

我们将上述函数称之为“便利函数”,因为它是通过使用共有成员函数完成一些成组或者组合的操作。将所有“便利函数”放在多个头文件中但隶属同一个命名空间,意味着客户可以轻松扩展这一组便利函数。 --- 就像C++标准库所做的那样。

Effective C++ (笔记) : 条款18 -- 条款23