列在这里的规则主要讨论C++ 在支持基于数据抽象和面向对象程序设计方面扮演的角色。也就是说,它们更多关注这个语言在支持思考和表达高层次思想方面扮演的角色,而不是它按C或Pascal的方式,作为一种“高级汇编语言”时扮演的角色。

C++设计支持规则

 

支持健全的设计概念:任何个别的语言特征都必须符合一个整体模式,这个整体模式必须能帮助回答一个问题——什么样的功能是我们需要的。语言本身不可能提供这种东西,这个指导模式必然来自另一个完全不同的概念层次。对C++ 而言,这个概念层次就是有关程序应该如何设计的基本思想。

我的目标是提升系统程序设计中的抽象层次,其方式类似于C语言在取代汇编语言作为系统工作主流时的所作所为。有关新特征的想法都放在这一统一框架中考虑,看它们在将C++ 提升为一种表述设计的语言时能起到什么作用。特别是对个别特征的考虑,就要看它能否形成一种可以通过类进行有效表述的概念。对于C++ 支持数据抽象和面向对象的程序设计,这是最关键的问题。

一个程序设计语言不是也不应该是一个完整的设计语言。因为设计语言应该更丰富,不必像适合做系统程序设计的语言那样过多地关心细节。但是,程序设计语言也应该尽可能直接地支持某些设计概念,以使设计师和程序员(这些人常常是“戴着不同帽子的”同一批人)之间更容易沟通,并能简化工具的构造。

采用设计的术语来观察程序设计语言,更容易基于该语言与其支持的设计风格之间的关系,去考虑接受或者拒绝人们建议的语言特征。没有一种语言能支持所有风格,而如果一个语言只支持某种定义狭隘的设计哲学,它也将因为缺乏适应性而失败。提升C++ 语言使之支持宽谱的设计技术,将它们映射到“更好的”C / 数据抽象 / 面向对象程序设计,使我们能在为发展提供持续动力的同时,避免把C++ 弄成所有人的唯一工具。

为程序的组织提供丰富的机制:与C语言相比,C++ 能帮助人们更好地组织程序,使之更容易书写、阅读和维护。我把计算看成已经由C语言解决的问题。和几乎所有人一样,我也有一些关于C表达式和语句应如何改进的想法。但我决定把自己的努力集中到其他方面。有关表达式或语句的新建议,都需要仔细评价,看它是能影响程序的结构呢,还是仅使表达某种局部计算变得更容易些。除了不多的例外,例如允许声明出现在第一次需要变量的位置(3.11.5节)等,C语言的表达式和语句结构都保持不变。

直接说出你的意思:低级语言有一个最本质的问题,那就是在人们互相交流时能如何表述问题,和他们在使用程序设计语言时能如何表述问题之间存在一条鸿沟。程序的基本结构常常被淹没在二进制位、字节、指针和循环等的泥潭中。

要缩小这种语义鸿沟,最基本的方法就是使语言更具有说明性。C++ 语言提供的每种机制都与使某种东西更具有说明性相关,然后是为了一致性检查、检测出愚蠢的错误,或者改进所生成的代码而开发的一些附加结构。

在无法使用说明性结构的地方,某种更明确的记法常常会有所帮助。分配/释放运算符(10.2节)和新的强制转换记法(14.3节)都是很好的例子。有关直接而明显地表达意图的思想,很早就有一种说法:“允许用语言本身表达所有重要的东西,而不是在注释里或者通过宏这类黑客手段。”这也意味着,一般而言,这个语言必须具有比原来的通用语言更强的表达能力和灵活性,特别是它的类型系统。

所有特征都必须是能够负担的:仅仅给用户提供一种语言特征,或者针对某个问题建议一种技术还不够。提供的解决方案还必须是能负担得起的,否则这个建议简直就是一种侮辱,就像对于提问“什么是到孟菲斯的最好方式”,你回答说“去租一架专机”一样。对不是百万富翁的人们而言,这绝不是一个有益的回答。

只有在无法找到其他方法,而且能在付出明显更低的代价并得到类似效果时,才应该把这个特征加进C++。我自己的经验是,如果程序员可以在高效地或优雅地做某种事情之间做选择,大部分人将选择效率,除非存在其他更重要更明显的原因。例如,提供inline函数是为了无代价地跨过保护边界,对于宏的许多使用而言,这都是另一种具有更好行为的选择。这里的思想是显然的:一种功能应该同时是优雅的而又是高效的。在无法同时达到这些的地方,或者不提供这种功能,或者是——如果要求非常迫切——高效地提供它。

允许一个有用的特征比防止各种错误使用更重要:你可以在任何语言里写出很坏的程序。真正重要的问题,是尽可能减少偶然用错某些特征的机会。我们花了很多精力,去保证C++ 里各种构造的默认行为或者是有意义的,或者将导致编译错误。例如,按默认方式,所有函数的参数类型都要做检查,即使是跨过了分别编译的边界;还有,按默认方式,所有的类成员都是私用的。当然,一个系统程序设计语言不应该禁止程序员有意打破系统的限制,所以设计的努力应该更多地放在提供一些机制,帮助人写出好程序方面,而不是放在禁止不可避免的坏程序方面。在长期的工作中,程序员必然会学习。这种观点也是C语言传统上的“相信程序员”口号的一种变形。提供各种类型检查和访问控制规则,使类的提供者能清楚地表述其对类的使用者期望些什么,并提供保护以防偶然事故。这种规则并不想提供一种保护机制,禁止有意违反规则的情况(2.10节)。

支持从分别开发的部分出发进行软件的组合:复杂应用需要比简单程序更多的支持,大程序需要比小程序更多的支持,效率约束很强的程序需要比资源丰富的程序更多的支持。在第三个条件的约束下,C++ 的设计中花了很多精力去解决前两个问题。当实际应用变得更大、更复杂时,这些应用必然是由一些人们能把握的具有一定独立性的部分组合而成的。

任何能用于独立进行大系统的部件开发,而后又允许将它们不加修改地用到大系统里的东西,都可以服务于这一目标。C++ 的许多发展都是由这一思想推动的。从根本上说,类本身就是这样的C++特征,抽象类(13.2.2节)能显式支持接口与实现分离。事实上,类可以用于表述一系列互相联系的策略 [Stroustrup,1990b]。异常机制允许从一个库出发去处理错误(16.1节),模板使人能基于类型进行组合(15.3节、15.6节和15.8节),名字空间解决名字污染问题(17.2节),而运行时类型识别能处理这样一类问题:当一个对象在传递过程中穿过一个库时,其准确类型有可能丢失,在这种情况下应该怎么办?

程序员在开发大系统时需要得到更多的帮助,还意味着不能过分依赖只对小程序有特效的优化技术,从而造成效率的损失。因此,对象布局应该能在特定编译单位内部孤立地确定,而虚函数调用也应该能编译成有效代码,不依靠跨越编译单位的优化。这些确实都做到了,甚至在高效意味着与C相比非常有效的意义下。如果有关整个程序的信息都能用,再做一些优化也是可能的。例如,通过检查整个程序,一个对虚函数的调用——在不牵涉动态链的情况下——有时可以确定为一个实际的函数调用。在这种情况下,就可以用一个正常的函数调用取代虚函数调用,甚至用inline的方式取代。现在已经存在能做这种事的C++ 实现。当然,对于生成高效代码,这种优化并不是必需的,它们不过是在希望更高的运行效率,而不是编译效率和动态连接新派生类的情况下,可以获得的一些附加利益。当无法合理地做到这类全局优化时,还是可以通过优化去掉一些虚函数调用,只要该虚函数是应用在已知类型的对象上,Cfront的Release 1.0就能做这件事。

相关文章: