第5章 隐藏实现
一个典型的C语言库通常包含一个struct和一些作用在这个struct上面的相关函数。迄今为止,我们已经看到C++怎样处理那些在概念上相关联的函数,并使它们在语义上真正关联起来,具体做法是:
把函数的声明放在一个struct的范围之内,改变这些函数的调用方法,在调用过程中不再把结构地址作为第一个参数传递,并增加一个新的数据类型到程序中(这样就不必为struct标记创建一个typedef之类的声明)。
这样做带来很多方便—有助于组织代码,使程序易于编写和阅读。然而,在使得C++中的库比以前更容易的同时,还存在一些其他问题,特别是在安全与控制方面。本章重点讨论结构中的边界问题。
5.1 设置限制
在任何关系中,设立相关各方都遵从的边界是很重要的。一旦建立了一个库,我们就与该库的客户程序员(client programmer)建立了一种关系,客户程序员需要用我们的库来编写应用程序或建立另外的库。
在C语言中,struct同其他数据结构一样,没有任何规则,客户程序员可以在struct中做他们想做的任何事情,没有什么途径来强制任何特殊的行为。比如,即使已经看到了第4章中提到的initialize()函数和cleanup()函数的重要性,但客户程序员有权决定是否调用它们(我们将在下一章看到更好的方法)。再比如,我们可能不愿意让客户程序员去直接操纵struct中的某些成员,但在C语言中没有任何方法可以阻止客户程序员这样做。一切都是暴露无遗的。
需要控制对结构成员的访问有两个理由:一是让客户程序员远离一些他们不需要使用的工具,这些工具对数据类型内部的处理来说是必需的,但对客户程序员解决特定问题的接口却不是必须的。这实际上是为客户程序员提供了方便,因为他们可以很容易地知道,对他们来说什么是重要的,什么是可以忽略的。
访问控制的理由之二是允许库的设计者改变struct的内部实现,而不必担心会对客户程序员产生影响。在上一章的Stack例子中,我们想以大块的方式来分配存储空间以提高速度,而不是在每次增加元素时调用malloc()函数来重新分配内存。如果这些库的接口部分与实现部分是清楚地分离并保护的,那么就能达到上述目的并且只需要让客户程序员重新连接一遍就可以了。