C++作为一种大型语言,为更大的设计空间开辟了道路。本章我们描述了一组基本设计规则和指南,在实践中已经证明这些规则和指南是有用的。
主要设计规则是绝对不能违反的。即使少量地违背主要设计规则,也可能危及大型系统的完整性。在本书中,我们会始终遵循所有的主要设计规则。
次要设计规则也是要遵守的,但不必严格地遵守。在个别的实例中偏离一个次要规则不会有严重的全局性的影响。
指南是作为经验法则提出来的,除非有令人信服的工程原因,否则必须遵守。
把一个类的数据成员暴露给客户端程序违反了封装原理。提供对数据成员的非私有访问意味着表示方法上的局部改变可能迫使用户重新编写代码。此外,由于允许对数据成员进行可写访问,偶然误用导致数据处在不一致的状态无法阻止。保护的成员数据就像公共成员数据一样,无法限制因数据改变而可能影响到的客户的数量。
全局变量会污染全局名字空间,而且会曲解设计的物理结构,使得几乎不能对代码进行独立的测试和有选择的重用。在新的C++项目中没有必要使用全局变量。我们可以通过将变量作为私有静态成员放置在一个类的作用域中,并提供公共静态成员函数访问它们的方法来系统地消除全局变量。但是,对这些模块的过度依赖是一种不良设计的症状。
自由函数,特别是那些不操作任何用户自定义类型的函数,在系统集成时很可能与其他的函数冲突。在类的作用域中嵌套这种函数作为静态成员几乎能够消除冲突的危险。
枚举、typedef以及常量数据也可能威胁全局名字空间。通过将枚举类型嵌套在类作用域中,任何歧义都可以通过作用域解析来消除。文件作用域中的typedef看起来很像类,而且在大型项目中极难发现。通过将typedef嵌套在类作用域中,它们就变得相对容易追踪。在头文件中定义的一个整数常量,最好是用类作用域中的一个枚举值表示。其他常量类型可以通过使它们成为某个类的静态常量成员来限定其范围。
预处理宏对人和机器来说都是难以理解的。由于不是C++语言的一部分,所以宏不被作用域约束,并且,如果放置在一个头文件中,宏可能与系统中的任一文件的任一标识符相冲突。因此,宏不应该出现在头文件中,除非作为包含卫哨。
从总体来看,除类、结构体、联合体和自由运算符外,我们应该避免在一个头文件中引入任何内容到作用域。当然,我们允许在头文件中定义内联成员函数。
重复包含一个定义将导致编译时错误。因为大多数C++头文件包含定义,保护程序,避免出现再收敛包含图,这是必不可少的。通过在一个头文件的内部使用内部包含卫哨包围定义,我们可以确保每个头文件在同一编译单元最多包含一次。
冗余(外部)包含卫哨,尽管不一定是必需的,但是可以确保我们避免编译时潜在的二次方复杂性的行为。通过在头文件中使用冗余卫哨包围include指令,我们可以确保一个头文件在同一编译单元最多被打开两次。
优秀的文档是软件开发必不可少的一部分。缺少文档会降低可用性。文档的一个重要部分包括对未定义情况的说明。无此说明,用户可能会渐渐地依赖仅仅由特定的实现选择导致的巧合行为。
不是所有的代码都必须是健壮的。在系统所有层次上都有冗余、运行时程序错误检查,可能对性能产生无法接受的影响。文档和断言(assertion)的结合可以达到同样的目的,并在最终产品中拥有卓越的运行时性能。
最后,通过提供一组一致的命名规则以区别数据成员、类型和常量,可以提高代码在开发小组之间的可读性。我们建议对数据成员使用一个“d_”前缀(如果是静态的,则使用“s_”前缀);用首字母大写表示一个类型,并用首字母小写表示一个变量或函数;用所有的字母都大写(包括下划线)表示枚举值、常量数据和预处理器常量;使用首字母大写分隔多个单词的标识符。我们也建议为诸如迭代器之类的循环设计模式使用一致的名称。