9.2.2 访问函数
在类中内联函数的最重要的使用之一是用做访问函数(access function)。这是一个小函数,它容许读或修改对象状态—一个或几个内部变量。从下面的例子中,可以看访问函数为内联函数的原因。
这里,在类的设计者控制下,将类里面状态变量设计为私有,类的使用者就永远不会直接和它们发生联系了。对私有数据成员的所有访问只能通过成员函数接口进行。而且,这种访问是相当有效的。例如对于函数read(),若没用内联函数,对read()调用产生的代码将包括对this压栈和执行汇编语句CALL。对于大多数机器,产生的代码将比内联函数产生的代码大一些,执行的时间肯定要长。
不用内联函数,考虑效率的类设计者将忍不住简单地使i为公共成员,从而通过让用户直接访问i来消除开销。从设计的角度看,这是很不好的。因为i将成为公共接口的一部分,所以意味着类设计者决不能修改它。我们将和称为i的一个int类型变量打交道。这是一个问题,因为可能在稍后觉得用一个float变量比用一个int变量代表状态信息更有用一些,但因为int i是公共接口的一部分,所以不能改变它。同样,想在读或是设置i值时执行加法运算也是不允许的,另一方面,假如总是使用成员函数读和修改一个对象的状态信息,那么就可以满意地修改对象内部一些描述。
另外,使用成员函数控制数据成员的访问允许在成员函数中增加代码以检测数据什么时候改变。这在程序调试时非常有用。如果数据成员是public的,任何人就可以任意改变它的值。
9.2.2.1 访问器和修改器
一些人进一步把访问函数的概念分成访问器(accessor)(用于从一个对象读状态信息)和修改器(mutator)(用于修改状态信息)。而且,可以用重载函数为访问器和修改器提供相同函数名,调用函数的方式决定了是读还是修改状态信息。
构造函数使用构造函数初始化列表(这在第7章中做了简介,在第14章中将做详细介绍)来初始化wide和high值(对于内建数据类型使用伪构造函数调用形式)。
不能让成员函数名与数据成员名相同,于是我们也许想用下划线作为标识符的第一字符来区分这些数据成员。然而,第一个字符为下划线的标识符是保留的,所以不应该使用它们。
可以选用“get”和“set”来标识访问器和修改器。
当然,访问器和修改器对于内部变量来说,不必是简单的管道。有时,它们可以执行一些比较复杂的计算。下面的例子使用标准的C库函数中的时间函数来生成简单的Time类:
标准C库函数对于时间有多种表示,它们都是类Time的一部分。但全部更新它们是没有必要的,所以time_t t被用作基本的表示法,tm local和ASCII字符表示法asciiRep都有一个标记来显示它们是否已被更新为当前的时间time_t。两个私有函数updateLocal()和updateAscii()检查标记,并有条件地执行更新操作。
构造函数调用mark()函数时(用户也可以调用它,强迫对象表示当前时间)也就清除了两个标记,这时当地时间和ASCII表示法是无效的。函数ascii()调用updateAscii(),因为函数ascii()使用静态数据,假如它被调用,则这个静态数据被重写,所以updateAscaii()把标准C库函数的结果拷贝到局部缓冲器里。函数ascii()返回值就是内部缓冲器的地址。
所有以daylightSavings()开始的函数都使用函数updateLocal(),这就使得复合的内联函数变得相当大。这似乎不划算,尤其是考虑到可能不经常调用这些函数。但这不意味着所有的函数都应该用非内联函数。如果想让其他一些函数成为非内联函数的话,也至少让updateLocal()为内联函数,这样它的代码将被复制在所有的非内联函数里,也能消除函数调用时额外的开销。
下面是一个小的测试程序:
在这个例子里,创建了一个Time对象,然后执行一些时延动作,接着创建第2个Time对象来标记结束时间。这些用于显示开始时间、结束时间和消耗的时间。