C++之父谈C++语言设计规则( 七 )


C++ 从C语言继承了许多特征 , 例如联合、强制转换和数组 , 这就使它不可能在编译时检查出所有违反类型系统的情况 。 这就是说 , 你需要显式使用联合、强制转换、数组、明确不加检查的函数参数 , 或者显式使用不安全的C连接去违反这里的类型系统 。 所有不安全特征的使用都可以处理为(在编译时)产生警告 。 更重要的是 , 目前的C++ 已经拥有了一些很好的语言特征 , 采用它们更方便而又同样有效 , 因此可以避免使用不安全的特征 。 这方面的例子包括派生类(2.9节)、标准数组模板(8.5节)、类型安全的连接(11.3节) , 以及动态检查的强制转换(14.2节) 。 由于与C语言兼容的需要以及常见的实践 , 维持目前这种状态的路还很长也很困难 , 但大部分程序员已经采纳了更安全的方式 。
只要有可能 , 总在编译时进行检查 。 只要有可能 , 那些在处理单独编译单位时只能提供信息而无法检查的东西 , 都要在连接时检查 。 最后 , 这里还提供了运行时的类型信息(14.2节)和异常机制(第16章) , 以帮助程序员处理编译和连接都无法捕捉的错误条件 。 当然 , 在实践中 , 还是编译时检查的代价更低 , 也更值得信赖 。
为用户定义类型提供与内部类型同样好的支持:因为我们把用户定义类型看作C++ 程序的核心 , 语言当然应该给它们尽可能多的支持 。 因此 , 例如“类对象只能在自由空间中分配”这样的限制就是无法接受的了 。 对于例如complex这样的算术类型 , 确实需要真正的局部变量 , 这也就导致了对于面向值的类型(实在类型)的支持不但可以与内部类型媲美 , 甚至还超过了它们 。
【C++之父谈C++语言设计规则】局部化是好事情:人们写一段代码时总希望它是自足的 , 除了可能需要从其他地方得到一些服务 。 也希望能使用一种服务又不带来过多的麻烦和干扰 。 另外 , 人们也需要为其他人提供函数和类等 , 同时不担心其实现细节与其他人的代码出现相互干扰 。
C语言距离这些思想都非常遥远 , 连接器可以看到所有全局函数和全局变量的名字 , 这些名字会与同样名字的其他使用互相冲突 , 除非显式将它们声明为static 。 任何名字都可以当作函数名使用而不必事先声明 。 作为早年把结构成员名也看成全局名的遗风 , 在一个结构里面声明的结构也是全局的 。 此外 , 预处理程序的宏处理根本不考虑作用域问题 , 因此 , 只要改变了头文件或编译器的某些选项 , 程序正文中的任意一段字符都可能变成另外的任何东西(18.1节) 。 如果你想去影响某些看起来是局部的代码 , 或希望通过某些小的“局部”修改影响整个世界的其他部分 , 上面这些东西的威力异常强大 。 公平地说 , 我认为这些东西对于理解和维护复杂的软件具有破坏性 。 因此决心提供更好的隔离手段 , 以对抗从“其他地方”来的破坏 , 对能从自己的代码中“引出”什么东西提供更好的控制 。
对于代码局部化、使访问总是通过良好定义的接口进行而言 , 类是第一位的最重要的机制 。 嵌套类(3.12节和13.5节)和名字空间进一步扩展了局部作用域和访问权的显式授予的概念 。 由于这些情况 , 在一个系统里的全局信息的总量大大减少了 。
访问控制使访问局部化 , 而且没有因为完全的隔离而造成运行时间或存储空间的额外开销(2.10节) 。 抽象类使人可以以最小的代价得到最大程度的隔离(13.2节) 。
在类和名字空间里 , 人们可以将声明和实现分开 , 这也非常重要 , 因为这样做使人更容易看到一个类到底做了些什么 , 而不必不断地跳过描述有关工作如何完成的函数体 。 允许在类声明中写inline函数 , 这样 , 当上述分离不合适时 , 可以得到另一种局部性 。
最后 , 如果代码中重要的块能放进一个屏幕 , 对于理解和操作也将大有裨益 。 C语言传统的紧凑性在这方面很起作用 , C++ 允许在需要使用的地方引进新变量(3.11.5节) , 也是在这个方向上前进了一步 。