有人说全局变量就像癌症一样:你不能够和它们生活在一起,但是一旦建立,通常是无法删除的。在一个新的C++项目中,我们总是可以避免,且无需使用外部全局变量。对于这个规则,例外的情况可能出现在用全局变量通信的复杂程序(像Lex或YACC)或嵌入式系统中。
主要设计规则
避免在文件作用域内包含带有外部链接的数据。
文件作用域中带有外部链接的数据,与存在于其他编译单元中的全局变量有冲突的危险(这些数据的作者太过于以自我为中心,他们认为自己拥有全局作用域)。但是“名称污染”只是全局变量破坏程序的许多方式之一。全局变量将对象和代码绑定在一起的方式,使得在其他的程序中几乎不可能选择性地重用编译单元。对于在大型项目中自由使用全局变量的系统,进行调试、测试、甚至理解的成本也可能变得非常惊人。
只要不是被迫使用一个已要求在其接口上使用全局变量的系统,就有简单的变换方式能将这些变量非全局化:
(1)将所有全局变量放入一个结构中;
(2)然后将它们私有化并添加静态访问函数。
假如我们有下列全局变量:
通过将这些变量放入一个结构内并使之成为该结构的静态成员,就可以把它们从全局名字空间中删除:
当然,要记住在相应的.c文件中定义这些静态数据成员。现在不要使用size、scale或system访问全局变量,应该使用Global::s_size、Global::s_scale或Global::s_system访问全局变量。命名冲突的概率现在减少到与单个类名相冲突的概率相等(应用7.2节论述的技术也容易解决这种概率的冲突)。
尽管已经解决了全局名字空间的问题,我们仍未做完应做的事情。经验表明,就像直接访问非静态成员数据(即特定于实例)一样,直接访问静态成员数据(即特定于类)会使得维护大型系统的成本极为昂贵。如果我们要把一个成员(如s_size)的导出数据类型从int变为double,则会改变接口;不管我们采取什么措施,所有的客户端程序都会受到影响。但是我们可以把s_size的实现变为一个基于其他更原始的值(如s_width和s_height)计算得到的值。倘若利用静态函数成员访问(和操纵)静态数据成员,则允许进行这种局部修改,而不会扰乱全局作用域的客户端程序。
下一步是通过建立一个Global类并为其添加静态操纵函数和访问函数方法来删除公共数据,如图2-3所示。Global类现在是一个在程序的任何地方都可以访问的逻辑模块。由于所有的接口函数都是静态的,所以没有必要为使用这个类实例化一个对象。将默认构造函数声明为私有并不实现,这样可以强制采用这种使用模式。
为了获得灵活的设计,我们应该谨慎小心,以免过度使用全局状态信息。如果我们只希望拥有一个对象的单个实例,那么把这个对象作为一个系统模块(如Global类)或者定义一个可实例化的类都能达到这一目的。定义成模块的好处是:这些变量可以代表系统内的唯一资源(如系统控制台)或系统级的常量(如limits.h文件中定义的变量),而不针对特定应用(参见6.2.9节)。当有其他的、更局部化的(如基于对象的)实现就能满足要求时,最好避免使用全局模块。