最近开始看《Effective C++》,为了方便以后回顾,特意做了笔记。若本人对书中的知识点理解有误的话,望请指正!!!
为什么不采用 public 成员变量呢?
首先从语法一致性(见条款18)开始。若成员变量不是 public ,用户只能通过成员函数来访问它。如果 public 域内都是函数,用户就不需要在访问 class 成员时迷惑地试着记住是否使用小括号()。
或许一致性的理由还不足以让人信服,那么从读写访问权限方面解释。如果成员变量声明为 public ,那么每个人都可以读写它,但是成员变量声明为 private ,你就可以控制成员变量的读写权限,通过函数访问的方式控制成员变量的读写。
若是还不能说服你,那么只能放出武器了:封装。如果你通过函数访问变量,日后有某个计算替换这个成员变量,而用户不会知道 class 的内部实现已经发生了变化。举个例子:
class SpeedDataCollection{ //速度收集器类
...
public:
void addValue(int speed); //把当前测得的速度放进数据集里
double averageSpeed() const; //利用数据集计算平均速度
..
};
那么如何实现成员函数 averageSpeed()
呢?
第一种方式:在 class 内设计一个成员变量,记录至今以来所有速度的平均值。当 averageSpeed()
被调用,只需返回那个成员变量。这种方式会使每一个 SpeedDataCollection
对象变大,因为你必须为用来存放目前平均值、累积总量、数据点数的每一个成员变量分配空间。然而 averageSpeed()
却可因此而十分高效
第二种方式:令 averageSpeed()
每次被调用时重新计算平均值,此函数有权调取收集器内的每一笔速度值。这种方式使用更少内存,只需要定义一个新的 inline
函数,但在被调用时需要现场计算,使用这个方式肯定要慢于第一种方式。
哪种方式更好,这个问题取决于使用环境。若在一部内存吃紧的机器上或在一个平均值使用不频繁的应用程序中,第二种方式更好;但在一个频繁使用平均值的应用程序中,如果反应速度非常重要,内存不是重点,那么第一种方式更好。
将成员变量隐藏在函数接口背后,可以为“所有可能的实现”提供弹性。例如:
- 可使得成员变量被读或被写时轻松通知其它对象
- 可以验证 class 的约束条件以及函数的前提和事后状态
- 可以在多线程环境中执行同步控制
- …
封装是很重要的。如果你对用户隐藏成员变量(也就是封装它们),你可以确保 class 的约束条件总是会获得维护,因为只有成员函数可以影响它们,同时保留了你以后变更实现的权利。如果你不隐藏它们,即使你有 class 源码,改变任何 public 事物的能力是极端受到束缚的,因为那会破坏太多使用 public 成员变量的代码。public 意味不封装,不封装意味不可改变。
protected 成员变量的论点十分类似。实际上它和 public 成员变量的论点相同,虽然最初看起来不是一回事。“语法一致性”和“访问权限控制”等理由也适用于 protected 数据,就像对 public 一样适用。但封装呢?protected 成员变量的封装性是不是高过 public 成员变量?答案并非如此。
条款23会告诉你:某些东西的封装性与“当其内容改变时可能造成的代码破坏量”成反比。因此成员变量的封装性与“成员变量的内容改变时可能造成的代码破坏量”成反比。所谓改变,也许是从 class 中移除它。
假设我们有一个 public 成员变量,而我们最终取消了它。这会导致所有使用它的代码被破坏,而那是一个不可知的大量。因此 public 成员变量没有封装性。
假设我们有一个 protected 成员变量,而我们最终取消了它。这会导致所有使用它的派生类都会被破坏,那也是个不可知的大量。因此protected 成员变量也缺乏封装性。
一旦你将成员变量声明为 public 或 protected ,一旦它发生改变,意味着将会迎来大量的工作,太多代码需要重写、重新测试、重新编写文档、重新编译。从封装的角度看,其实只有两种访问权限:private(提供封装)和其它(不提供封装)。
Note:
- 切记将成员变量声明为 private,这给用户提供语法一致性,精确的访问权限控制,允诺约束条件获得保证,各种功能实现的弹性。
- protected 并不比 public 更具封装性