如果地图与地表不符,要相信地表。——瑞士军队格言

C++ 的设计规则——整体设计目标——社会学规则——C++ 作为一种支持设计的语言——语言的技术性规则——C++ 作为一种支持低级程序设计的语言

4.1 规则和原理

要成为真正有用而且人们乐于使用的东西,一个程序设计语言的设计就必须有一种全局观,用于指导语言中各种特征的设计。对于C++,这种全局观由一组规则和约束构成。称其为规则,是因为我认为把原理这个词用在一个真正的科学原理非常贫乏的领域,显得过于自命不凡,而程序设计语言设计就是这样一个领域。此外,对许多人而言,术语原理隐含着一个不太实际的推论,也就是说,任何例外都是不可接受的。而我的有关C++设计的规则几乎可以保证都有例外情况。实际上,如果一条规则与某个实际试验发生冲突,这个规则就应该靠边站。这样说,看起来似乎有些粗鲁,但是它不过是一条一般性原则的变形:理论必须与试验数据相吻合,否则就应该被更好的理论取代。

这些规则绝不能不加思索地使用,也不能用几条肤浅的口号取代。我把自己作为一个语言设计者的工作看作去决定需要对付的是哪些问题,决定在C++ 的框架里能够对付的是哪些问题,并在实际语言特征设计的各种规则之间保持一种平衡。

这些规则指导着与语言特征有关的各项工作。当然,改进设计的大框架是由C++ 的基本设计目标提出来的。

我把这些规则组织在四个更具概括性的小节里。4.2节包含所有与整个语言有关的规则,这些东西非常具有普遍性,单个的语言特征将无法直接放进这个图景里。4.3节的规则基本上与C++ 在支持设计方面所扮演的角色相关。4.4节的规则关注与语言的形式有关的各种技术细节。4.5节的规则集中关注C++ 作为低级系统程序设计语言所扮演的角色。

这些规则的形式主要得益于事后的思索,但有关规则及其所表达的观点,在1985年C++的第一个发布之前就已经支配着我的思想了,而且——正如前面章节里讲到的——这些规则中不少还是C with Classes的初始概念的组成部分。

4.2 一般性规则

最一般和最重要的C++ 规则与语言的技术方面没太大关系,这些规则几乎都是社会性的,其关注点是C++ 所服务的社团。C++ 语言的本质在很大程度上出于我的选择,我认为它应该服务于当前的这一代系统程序员,支持他们在当前的计算机系统上解决当前遇到的问题。最重要的是,当前这个词的意义和性质总随着时间而变化,C++ 必须能够发展,以满足其用户的需要;它的定义不应该是一成不变的。

C++ 的发展必须由实际问题推动:在计算机科学中,就像在许多其他领域一样,我们总能看到许多人在努力为他们最喜爱的解决办法寻找问题。我不知道有任何简单明了的方法能避免时尚扭曲我对什么最重要的认识,但是我也很敏锐地意识到,提供给我的许多语言特征在C++ 的框架里根本就不可行,它们常常与真实世界的程序员无关。

改变C++ 的正确推动力,是一些互相独立的程序员证明该语言不能很好地表述他们的项目。我偏爱来自非研究性项目的信息。无论何时只要可能,在努力发现问题和寻找解决办法时,我设法与真实的用户联系。我如饥似渴地阅读程序设计语言文献,寻找对这些问题的解答,以及各种可能有帮助的技术。但是我也发现,文献在考虑真正的问题方面完全不可靠,理论本身不可能为加入或去掉一个特征提供充分的证据。

不被牵涉到无益的对完美的追求中:任何程序设计语言都不是完美的。由于问题和系统都在持续变化,将来也不可能有完美的语言。用许多年功夫去修饰一个语言,希图去接近某种完美的概念,只能是使程序员无法从那些年的进步中获益,也使语言设计者不能得到真实的反馈。没有适当的反馈,一个语言就会逐渐与时代脱节。不同环境里的问题,各种计算机系统,以及——最重要的——人与人之间,都存在巨大的差异,因此,对某些小环境的“完美配合”几乎可以确定是过分特殊的,与大千世界的繁荣没多大关系。另一方面,程序员花费了他们的大部分时间去修改老代码,或者与它们接口。为了完成实际工作,他们需要某种稳定性。一旦某个语言投入了实际应用,对它的剧烈修改就不可行了,甚至想做一点小修改而又不伤害到用户,也是很困难的。因此,重要的改进需求必须依靠真实的反馈,并伴以对兼容性、转变过程和教育的认真考虑。随着语言逐渐成熟,人必须更多考虑通过工具、技术和库的替代性方式,而不是去改变语言本身。

并不是每个问题都需要用C++ 解决,也不是说C++ 的每个问题都足够重要,值得做一种解决方案。例如,完全没必要扩充C++ 去直接处理模式匹配或定理证明; C语言著名的运算符优先级的毛病(2.6.2节)也最好让它待在那里,或通过警告信息去处置。

C++ 必须现在就是有用的:许多程序设计是现世的,用在功能较差的计算机上,运行在相对过时的操作系统和工具上。大部分程序员没经过应有的形式化训练,几乎没时间去更新他们的知识。为了能够为这些程序员服务,C++ 必须能适合具有平均水平的人,能用于平均水平的计算机。

虽然我也多次想过尝试,但我从来就没有真正的欲望去抛弃这些人以获得某种自由,去调整我的设计以满足最尖端的计算机和计算机科学研究者们的口味。

这个规则的意义——与大部分其他规则一样——也将随着时间推移而改变,部分地是C++ 成功的结果。威力更强大的计算机今天已经可用了,更多程序员接受了C++ 所依赖的基本概念和技术。进一步说,随着人的抱负和期望的增长,程序员所面对的问题也在改变。这也意味着要求更多计算机资源,要求更成熟的程序员的某些语言特征,今天已经可以并且应该考虑了。异常处理(第16章)和运行时类型识别(14.2节)就是这方面的例子。

每个特征必须有一种合理的明显实现方式:不应该有必须通过复杂的算法才能正确地或有效地实现的特征。理想地说,应该存在明显的分析和代码生成策略,而这些应该足够应付实际使用。如果更多思考能产生更好的结果,当然是越多越好。大部分特征都通过实现、试验性的使用、检查修订,而后才被接受。那些没能按这种方式去做的地方,例如模板的实例化机制(15.10节),后来就暴露出了一些问题。

当然,使用者总比写编译器的人多得多,所以,如果在编译复杂性和使用复杂性之间出现了需要权衡的问题,解决方案必定是偏向用户的。我在许多年做编译器维护的工作中,已经真正理解了这个观点。

总提供一条转变的通路:C++必须逐渐成长,以便很好地服务于它的用户,并从用户的反馈中获益。这里隐含着特别关注和保证老代码能继续使用的问题。当某种不兼容性已经无法避免时,需要特别关注如何帮助用户更新他们的代码。类似地,必须提供一条路径,使人能从容易出错的C一类的技术转到C++的更有效的使用方面来。

要清除一种不安全、容易出错,或者简单说就是有毛病的语言特征,最一般的策略是首先提供一种更好的替代品,而后建议人们避免使用老的特征或技术,而只有到数年之后——如果需要做的话——再删除那个有问题的特征。可以有效地通过让编译产生警告信息的方式支持这种策略。一般说,直接删除一个特征或者更正一个错误是不可行的(典型原因是为了保持与C的兼容性):替代的方法是提出警告(2.6.2节)。这样做,还能使C++ 的实现比仅根据语言定义看到的情况更安全些。

C++是一种语言,而不是一个完整的系统:一个程序设计环境包含了许多部分。一种方式是将多个部分组合成一个“集成化的”系统,另一种方式是维持系统中各部分之间的经典划分,例如编译器、连接器、语言的运行支持库、I/O库、编辑器、文件系统、数据库,等等。C++ 遵循的是后一条路。通过库、调用约定等,C++ 能适应各种系统里指导着语言和工具之间互操作的系统规定。对于移植和实现的简单性,这些都是非常关键的。更重要的,对支持不同语言写出的代码之间的互操作,这些做法也很关键。这种方式也能允许工具的共享,使作为个人的程序员能更容易地使用多种语言。

C++ 的设计就是准备作为许多语言之中的一个。C++ 支持工具的开发,但又不强求某种特定的形式,程序员仍然有选择的自由。这里的关键思想是,C++ 及其关联工具应该对给定系统有正确的“感觉”,而不是对于什么是一个系统,或者什么是一个环境强加上某种特殊的观点。对于大型系统,或者有着不同寻常的约束的系统而言,这些非常重要。这类系统常常无法得到很好的支持,因为“标准的”系统的设计总是倾向于专门支持个人,或者是支持很小的组,仅仅支持他们做相当“普通的”工作。

为每种被支持的风格提供全面支持:C++ 必须为满足严肃的开发者们的需要而不断发展。简洁是最基本的东西,但应根据将要使用C++ 的项目的复杂性来考虑这个问题。与保持语言定义比较简短相比,用C++ 写出的系统的可维护性和运行时的性能是更重要的问题。这实际上意味着要做的是一个规模相对较大的语言。

这也意味着——正如许多经验所说明的——必须支持各种风格的程序设计。人们并不是只写那些符合狭义的抽象数据类型或者面向对象风格的类,他们也要写同时具有两方面特点的类,这样做通常都有很好的理由。他们还会写这样的程序,其中的不同部分采用了不同的风格,以适应具体需要或者是个人口味。

因此,语言特征应该设计成能以组合的方式使用,这也导致了C++ 设计中相当程度的正交性。支持各种“非正常”使用的可能性,对于灵活性提出了很高要求,这已经一再地导致C++ 被用到一些领域中,在那里,更局限更专注语言可能早就失败了。例如,C++ 中有关访问保护、名字检索、virtual / 非virtual约束,以及类型的规则都是相互独立的,这样就打开了一种可能,同时使用依赖于信息隐蔽和派生类的各种技术。有些人愿意看到一贯语言只支持很少几种程序设计风格,他们会认为这里的做法是“黑科技”。在另一方面,正交性并不是第一原则,只有在其不与其他规则冲突,既能提供某些利益而又不使实现复杂化的时候,我们才采纳它。

做一种相对较大的语言,也意味着在复杂性管理方面的努力需要有所转移,从对于库和个别程序的理解方面,转到学习语言及其基本设计技术。对大部分人而言,这种重点转变,对新程序设计技术的采纳,对“高级”技术的使用,都只能逐步完成。很少人能够“一蹴而就”地完全掌握新技术,或者一下就能把所有的新招术都用到自己的工作里(7.2节)。C++在设计中考虑了如何使这种渐变成为可能的和自然的。这里的基本思想是:你不知道的东西不会伤害你。静态类型系统和编译的警告信息在这方面非常有帮助。

不试图去强迫人做什么:程序员都是很聪明的人,他们从事挑战性的工作,需要所有可能的帮助,不仅是其他支撑工具和技术,也包括程序设计语言。试图给程序员强加严格的限制,使他们“只能做的正确事情”,从本质上说是搞错的方向,也注定会失败。程序员总能找到某种方法,绕过他们觉得无法接受的规则和限制。语言应该支持范围较广泛的、合理的设计和编程风格,而不应该企图去强迫程序员采纳某种唯一的写法。

当然,这并不意味着所有的程序设计方式都同样好,或者说C++ 应该支持所有种类的程序设计风格。C++ 的设计是为了直接支持那些依靠广泛的静态类型检查、数据抽象和继承性的设计风格。当然,关于应该使用哪些特征的训教被维持到最小的程度。语言机制尽可能保持了一种自由的政策,没有专门为了排斥任何确定的程序设计风格,而向C++ 里加进或者从中减去一种特征。

我也很清楚地意识到,并不是每个人都赞赏选择和变化。无论如何,那些偏爱带有较多限制环境的人,可以在C++ 里始终如一地坚持使用某些规则,或者去选用另一种语言,其中只给程序员提供了很少的一组选择。

许多程序员特别反感被告之某种东西可能是一个错误,而实际上它并不是。所以,“可能的错误”在C++ 里并不是一个错误。例如,写一个能允许歧义使用的声明本身并不是错误,错的是那些存在歧义性的使用,而不只是这个错误的可能性。按照我的经验,多数“潜在错误”根本不会显现出来,因此推迟错误信息的方式就是不把它给出来。这种推迟也会带来许多方便和灵活性。

4.3 设计支持规则

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

支持健全的设计概念:任何个别的语言特征都必须符合一个整体模式,这个整体模式必须能帮助回答一个问题——什么样的功能是我们需要的。语言本身不可能提供这种东西,这个指导模式必然来自另一个完全不同的概念层次。对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就能做这件事。

对大系统的支持,经常是在“对于库的支持”的题目下讨论的(第8章)。

4.4 语言的技术性规则

下面规则针对的问题是如何在C++里表述各种事物,这里不讨论能表述什么。

不隐式地违反静态类型系统:每个对象在建立时就具有特定的类型,例如double、char*或dial_buffer等。如果以与对象的类型不一致的方式去使用它,那就是违背了类型系统。绝不允许这种情况发生的语言称为是强类型的。如果一种语言能在编译时确认所有违反类型系统的情况,那么它就是强静态类型的。

C++ 从C语言继承了许多特征,例如联合、强制转换和数组,这就使它不可能在编译时检查出所有违反类型系统的情况。这就是说,你需要显式使用联合、强制转换、数组、明确不加检查的函数参数,或者显式使用不安全的C连接去违反这里的类型系统。所有不安全特征的使用都可以处理为(在编译时)产生警告。更重要的是,目前的C++ 已经拥有了一些很好的语言特征,采用它们更方便而又同样有效,因此可以避免使用不安全的特征。这方面的例子包括派生类(2.9节)、标准数组模板(8.5节)、类型安全的连接(11.3节),以及动态检查的强制转换(14.2节)。由于与C语言兼容的需要以及常见的实践,维持目前这种状态的路还很长也很困难,但大部分程序员已经采纳了更安全的方式。

只要有可能,总在编译时进行检查。只要有可能,那些在处理单独编译单位时只能提供信息而无法检查的东西,都要在连接时检查。最后,这里还提供了运行时的类型信息(14.2节)和异常机制(第16章),以帮助程序员处理编译和连接都无法捕捉的错误条件。当然,在实践中,还是编译时检查的代价更低,也更值得信赖。

为用户定义类型提供与内部类型同样好的支持:因为我们把用户定义类型看作C++ 程序的核心,语言当然应该给它们尽可能多的支持。因此,例如“类对象只能在自由空间中分配”这样的限制就是无法接受的了。对于例如complex这样的算术类型,确实需要真正的局部变量,这也就导致了对于面向值的类型(实在类型)的支持不但可以与内部类型媲美,甚至还超过了它们。

局部化是好事情:人们写一段代码时总希望它是自足的,除了可能需要从其他地方得到一些服务。也希望能使用一种服务又不带来过多的麻烦和干扰。另外,人们也需要为其他人提供函数和类等,同时不担心其实现细节与其他人的代码出现相互干扰。

C语言距离这些思想都非常遥远,连接器可以看到所有全局函数和全局变量的名字,这些名字会与同样名字的其他使用互相冲突,除非显式将它们声明为static。任何名字都可以当作函数名使用而不必事先声明。作为早年把结构成员名也看成全局名的遗风,在一个结构里面声明的结构也是全局的。此外,预处理程序的宏处理根本不考虑作用域问题,因此,只要改变了头文件或编译器的某些选项,程序正文中的任意一段字符都可能变成另外的任何东西(18.1节)。如果你想去影响某些看起来是局部的代码,或希望通过某些小的“局部”修改影响整个世界的其他部分,上面这些东西的威力异常强大。公平地说,我认为这些东西对于理解和维护复杂的软件具有破坏性。因此决心提供更好的隔离手段,以对抗从“其他地方”来的破坏,对能从自己的代码中“引出”什么东西提供更好的控制。

对于代码局部化、使访问总是通过良好定义的接口进行而言,类是第一位的最重要的机制。嵌套类(3.12节和13.5节)和名字空间进一步扩展了局部作用域和访问权的显式授予的概念。由于这些情况,在一个系统里的全局信息的总量大大减少了。

访问控制使访问局部化,而且没有因为完全的隔离而造成运行时间或存储空间的额外开销(2.10节)。抽象类使人可以以最小的代价得到最大程度的隔离(13.2节)。

在类和名字空间里,人们可以将声明和实现分开,这也非常重要,因为这样做使人更容易看到一个类到底做了些什么,而不必不断地跳过描述有关工作如何完成的函数体。允许在类声明中写inline函数,这样,当上述分离不合适时,可以得到另一种局部性。

最后,如果代码中重要的块能放进一个屏幕,对于理解和操作也将大有裨益。C语言传统的紧凑性在这方面很起作用,C++ 允许在需要使用的地方引进新变量(3.11.5节),也是在这个方向上前进了一步。

避免顺序依赖性:顺序依赖性很容易使人感到困惑,在重新组织代码时也容易引进错误。人们都会注意到,语句将按定义的顺序执行,但却往往会忽视全局声明之间和类成员声明之间的相互依赖性。重载规则(11.2节)和基类的使用规则(12.2节)都经过了特殊处理,避免其中出现对顺序的依赖性。理想情况是,如果交换两个声明的顺序会导致另一种不同的意思,那么这就应该是一个错误。对于类成员的规则就是这样(6.3.1节)。但是,对全局声明不可能做到这一点。C预处理程序可以通过宏处理引进根本无法预期的病态依赖性,从而可能造成很大的破坏(18.1节)。

我在某个时候曾经表达过有关避免微妙的解析方式的愿望,说:“帮助你下决心不应该是编译器的事情”。换句话说,产生编译错误将比产生某种含糊的解析更容易接受。多重继承的歧义性规则是这方面的好例子(12.2节)。关于重载函数的歧义性规则是另一个例子,它也说明了在兼容性和灵活性的约束下,要做好这件事有多么困难(11.2.2节)。

如果有疑问,就选择该特征的最容易说清楚的形式:这是在不同可能性之中做选择的第二规则,使用起来很有技巧性,因为它有可能变成一种关于逻辑美的争论,而且可能与熟悉不熟悉有关。写出描述它的辅导材料和参考手册,看看人们是否容易理解,是这个规则的一种实践方式。这里的一个意图就是简化教学人员和维护人员的工作。还应该记住,程序员们不笨,不应该通过付出重要的功能方面的代价去换取简单性。

语法是重要的(常以某些我们不希望的方式起作用):保证类型系统的一致性,一般而言,保证语言的语义清晰、定义良好,是最基本的东西。语法是第二位的,而且,看起来人们能学会去喜爱任何语法形式。

当然,语法就是人们看到的东西,也是语言最基本的用户接口。人们喜爱某些形式的语法,并以某种奇特的狂热去表达他们的意见。我认为,想改变这些东西,或者不顾人们对某些特定语法的对抗情绪去引进新的语义或设计思想,都是没希望成功的。因此,C++ 的语法,在设法使其更合理和规范的同时,也尽可能避免去触犯程序员们的成见。我的目标是逐渐使一些讨厌的东西淡出,例如隐含的int(2.8.1节)和老风格的强制(14.3.1节)等,同时又尽可能地减少使用更复杂形式的声明符语法(2.8.1节)。

我的经验是,人们往往过分热衷于通过关键词来引进新概念,以至于如果一个概念没有自己的关键词,教起来就非常困难。这种作用是很重要的、根深蒂固的,远远超过人们口头上表述的对新关键词的反感。如果给他们一点时间去考虑并做出选择,人们无疑地会选择新的关键词,而不是某种聪明的迂回方案。

我试着把重要操作做成很容易看见的东西。例如,老风格强制的一个重要问题是它们几乎不可见。此外,我也喜欢把语义上丑陋的操作在语法上也弄成丑陋的,与语义相匹配,例如病态的类型强制(14.3.3节)。一般说,也应该避免过分啰嗦。

清除使用预处理程序的必要性:如果没有C预处理程序,C语言本身和后来的C++ 可能早就是死胎了。没有Cpp,它们根本就不能有足够的表达能力和灵活性,不能处理重要项目中所需要完成的各种任务。但在另一方面,Cpp丑陋低级的语义也使构造和使用更高级、更优雅的C程序设计环境变得过分困难、代价过分昂贵。

因此,必须针对Cpp的每个基本特征,找到符合C++ 语法和语义的替代品。如果这个工作能够完成,我们就可能得到一个更便宜的大大改进的C++ 程序设计环境。沿着这个方向,我们也将清除许多很难对付的错误的根源。模板(第15章)、inline函数(2.4.1节)、const(3.8节)和名字空间(第17章)都是在这个方向上留下的脚印。

4.5 低级程序设计支持规则

很自然,上面提出的规则基本上都适用于所有语言特征。下面的规则也同样影响C++ 如何作为一种表述高层设计的语言。

使用传统的(笨[1])连接器:初始目标就包括了容易移植,容易与用其他语言写的软件互操作。强调C++ 应该可以用传统连接器实现,就是为了保证这些东西。要求通过在Fortran早期就有的连接技术必然是一件很痛苦的事情。C++ 的一些特征,特别是类型安全的连接(11.3节)和模板(第16章)都可以用传统的连接器实现,但如果有更多的连接支持,它们还可以实现得更好。C++ 的第二个目标就是想推动连接器设计的改革。

采用传统连接器,使维持与C的连接兼容性变得相对简单。对平滑地使用操作系统功能,使用C和Fortran等,使用库,以及写为其他语言所用的库代码,这一特性都具有本质性。对于写想作为系统底层部分的代码,例如设备驱动程序等,使用传统连接器也是最关键的事情。

没有无故的与C的不兼容性:C语言是有史以来最成功的系统程序设计语言。数以十万计的程序员熟悉C,现存的C代码数以十亿行计,存在着集中关注C语言的工具和服务产业。而且C++ 又是基于C的。这就带来一个问题:“C++ 的定义在与C相匹配方面到底应该靠得多么近?”C++ 不可能把与C的100% 兼容作为目标,因为这将危及它在类型安全性和对设计的支持方面的目标。当然,在这些目标不会受到干扰的地方,应该尽量避免不兼容性——即使这样做出的结果不太优雅。在大部分情况下,已经接受的与C语言的不兼容性,都出现在C规则给类型系统留下重大漏洞的地方。

在过去这些年里,C++ 最强的和最弱的地方都在于它与C的兼容性。这种情况不奇怪。与C兼容性的强弱将来也一直会是一个重要议题。在今后的年代里,与C的兼容性将越来越少地看作是优点,而更多变成一种义务。必须找到一条发展的道路(第9章)。

在C++ 之下不为更低级的语言留下空间(除汇编语言之外):如果一个语言的目标就是真正成为高级的——也就是说,它想完全保护自己的程序,使之避开基础计算机中丑陋且使人厌倦的细节——那么它就必须把做系统程序设计的工作让给其他语言。典型情况下,这个语言就是C。但另一种情况也很典型,C在许多领域中将取代这种高级语言,只要在这里控制或速度被认为是最关键的问题。常见情况是,这最终将导致整个系统完全用C语言来编写;或者是导致这样的一个系统,只有对两种语言都非常熟悉的人才能够把握它。在后一情况下,程序员常常会遇到一个艰难的选择:给定的任务究竟适合在哪个层次上做程序设计呢,他不得不同时记住两种语言的原语和准则。C++ 试图给出另一条路,它同时提供了低级特征和抽象机制,支持用这两种东西构造混合的系统。

为了继续成为一种可行的系统程序设计语言,C++ 必须保持C语言的那种直接访问硬件、控制数据结构布局的能力,保有那些能以一对一的风格直接映射到硬件的基本操作和数据结构。这样,它的替代品就只能是C或者汇编语言。语言设计的工作就是去隔离这些低级特征,使不直接操作系统细节的代码不需要用这些低级特征。这里的目标是保护程序员,防止出现无意中越界的偶然的错误使用。

对不用的东西不需要付出代价(0开销规则):对于规模较大的语言,有一种论断人人皆知,说它们会产生大而慢的结果代码。最常见的是由于支持某些假设的高级特征而产生的额外开销,而这种开销又散布在整个语言的所有特征中。例如,所有对象都需要扩大,以保存为某种系统簿记而使用的信息;对所有数据都采用间接访问方式,因为某些特征通过间接访问特别容易管理;或是对各种控制结构都进行加工,以迎合某种“高级控制抽象”。对C++ 而言,这类“分布式增肥”根本就不合适,接受它就会在C++ 之下为更低级的语言留下空间,使得对那些低级和高性能的应用而言,C语言将成为比C++更好的选择。

这个规则在C++的设计决策中不断成为最关键的考虑。虚函数(3.5节)、多重继承(12.4.2节)、运行时的类型识别(14.2.2.2节)、异常处理和模板,都是与此有关的特征实例,它们的设计也部分地可以归于这条规则,都是到了我自己已经确信能构造出遵守0开销规则的实现方式时,这些特征才被接受进来。当然,一个实现者可以决定在0开销规则和系统所需要的某些性质之间如何做一种折中,但是也必须仔细地做。程序员通常对于分布式的增肥会有刺耳的、非常情绪化的反应。

如果想拒绝人们建议的一个特征,0开销规则可能是所有规则中最锋利的一个。

遇到有疑问的地方就提供手工控制的手段:我对信任“高级技术”总是非常勉强,也特别不愿意去假定某些真正复杂的东西是普遍可用的和代价低廉的。inline函数是这方面的一个很好的例子(2.4.1节)。模板初始化是另一个例子,我在那里应该更当心一点,后来又不得不增加了一种显式控制的机制(15.10节)。对存储管理的细节控制也是一个例子,通过手工控制可能得到重要的收获,然而,只有时间才能告诉我们这些收获是不是可以通过某种自动化技术以类似的代价得到(10.7节)。

4.6 最后的话

对于各种主要的语言特征,所有这些规则都必须考虑。忘掉任何一个,都很可能带来某种不平衡,从而伤害到一部分用户。与此类似,让某个规则成为主导而损害其他方面,同样可能带来类似的问题。

我试着把这些规则都陈述为正面的命令式的句子,而不是构造出一个禁止表。这可以使它们从本质上说更不容易被用于排除新思想。我对C++ 的观点是,它是一种生产软件的新语言,特别关注那些影响程序结构的机制。与只做一些小调整的自然倾向相比,这种观点走的完全不是同一条路。

ANSI/ISO委员会工作组对语言的扩充提出了一个检查表(6.4.1节),那是对一个语言特征应该考虑的问题的更特殊、更细节的描述。


[1] 笨(dumb)连接器,指那些传统的最基本的,不提供任何特殊功能的连接器,也就是一般的系统上普遍使用的连接器。——译者注

本文摘自C++语言之父jarne Stroustrup的经典著作《C++语言的设计和演化》。

在传统上,关于程序设计和程序设计语言的书都是在解释某种语言究竟是什么,还有就是如何去使用它。但无论如何,有许多人也很想知道某个语言为什么会具有它现在的这个样子,以及它是怎样成为这个样子的。本书就是想针对C++ 语言,给出对后面这两个问题的解释。在这里要解释C++ 怎样从它的初始设计演化到今天的这个语言,要描述造就了C++ 的各种关键性的问题、设计目标、语言思想和各种约束条件,以及这些东西又是如何随着时间的推移而变化的。

当然,C++ 语言和造就它的设计思想、编程思想自身不会演化,真正演化的是C++ 用户们对于实际问题的理解,以及他们对于能够帮助解决这些问题的工具的理解。因此,本书也将追溯人们用C++ 去处理的各种关键性问题,以及实际处理那些问题的人们的认识,这些都对C++ 的发展产生了重要影响。

C++ 仍然是一个年轻的语言,许多用户对这里将要讨论的一些问题还不知晓。这里所描述的各种决策的进一步推论,可能还需要一些年才能变得更清晰起来。本书要展示的是我个人关于C++ 如何出现、它是什么以及它应该是什么的观点。我希望这些东西能帮助人们理解怎样才能最好地使用C++,理解C++ 的正在继续进行的演化进程。

书中特别要强调的是整体的设计目标、现实的约束以及造就出C++ 的那些人们。有关各种语言特征的关键性设计决策的讨论被放到了相应的历史环境里。这里追溯了C++ 的演化过程,从C with Classes开始,经过Release 1.0和2.0,直到当前ANSI/ISO的标准化工作,讨论了使用、关注、商业行为、编译器、工具、环境和库的爆炸性增长,还讨论了C++ 与C、Simula之间关系的许多细节。对于C++ 与其他语言的关系只做了简短讨论。对主要语言功能的设计,例如类、继承、抽象类、重载、存储管理、模板、异常处理、运行时类型信息和名字空间等,都在一定细节程度上进行了讨论。

本书的根本目的,就是想帮助C++ 程序员更好地认识他们所用的语言,该语言的背景和基本概念;希望能激励他们去试验那些对他们而言全新的C++ 使用方式。本书也可供有经验的程序员和程序设计语言的学生阅读,有可能帮助他们确定使用C++ 是不是一件值得做的事情。

C++之父谈C++语言设计规则相关推荐

  1. python语言中文社区-Python 之父谈 Python-Go语言中文社区

    在宣传海报上,Python 之父 Guido van Rossum 在 EuroPython 2015 会议的发言分为讲话稿和现场问答部分,但是他上台后将全程改为现场问答的形式.他在回答现场观众的问题 ...

  2. BIM建筑环境规则和分析(BERA)语言介绍(四)第三章 BERA语言设计

    设计策略 什么是好的语言设计?没有明确的答案,但许多研究人员 声称,新语言有重要因素可以获得接受和 长寿.就领域特定语言的主要目的而言,新语言 应首先以一种简单的方式有效地解决新问题[Mashey,2 ...

  3. 听Ruby之父畅谈编程语言的设计

    本文摘自<松本行弘:编程语言的设计与实现> 1-1 自己创造编程语言的意义 通过实际创造一门新的编程语言,可以学到编程语言的设计思路和实现方法.随着开源的普及,创造新编程语言的门槛一下子降 ...

  4. Go在谷歌:以软件工程为目的的语言设计

    From: http://www.oschina.net/translate/go-at-google-language-design-in-the-service-of-software-engin ...

  5. 嵌入式软件开发工程师谈软件架构的设计

    嵌入式软件开发工程师谈软件架构的设计 注:此处嵌入式特指基于linux平台,单片机和其他rtos不在讨论范围 笔者从事嵌入式软件开发有6,7个年头,bsp,驱动,应用软件,android hall,f ...

  6. c语言 去掉双引号_技术分享|浅谈C语言陷阱和缺陷

    良好的软件架构.清晰的代码结构.掌握硬件.深入理解C语言是防错的要点,人的思维和经验积累对软件可靠性有很大影响.C语言诡异且有种种陷阱和缺陷,需要程序员多年历练才能达到较为完善的地步.软件的质量是由程 ...

  7. 基于C语言设计符号表

    基于C语言设计符号表 c-语言的语法描述 系统设计 符号表的实现 符号表采用了哈希表的形式,可以方便地查找.插入和删除,但是问题也随之而来,就是符号的作用于较难跟踪.很有可能同一名称的变量在不同作用于 ...

  8. [转]Go在谷歌:以软件工程为目的的语言设计

    来源:http://blog.jobbole.com/36480/ 编译:oschina,原文:Go at Google: Language Design in the Service of Soft ...

  9. c语言弱符号与函数指针,浅谈C语言中的强符号、弱符号、强引用和弱引用【转】...

    首先我表示很悲剧,在看<程序员的自我修养--链接.装载与库>之前我竟不知道C有强符号.弱符号.强引用和弱引用.在看到3.5.5节弱符号和强符号时,我感觉有些困惑,所以写下此篇,希望能和同样 ...

  10. [C++程序语言设计笔记一]面向对象编程抽象,继承,重写基本介绍

    今天是个不错的日子,不仅有人收了我做徒弟从此传授我有关C++的一些知识,由于前一段时间喜欢上了外挂的研究也用到了一些MFC的知识及一些Windows APIs编程,但是对C++还是没有从根本上认识.我 ...

最新文章

  1. python四大软件-Python未来可能面临的四大转折
  2. basename函数使用
  3. dotween曲线运动 unity_Unity中DOTween插件的DOTweenPath轨迹移动
  4. 编写.gitignore文件
  5. html 广告 ins 原理,如何通过HTML DOM元素显示AdSense广告
  6. DWZ (JUI) 教程 dwz框架 刷新dialog解决方案
  7. 索佳电子水准数据传输软件_安徽铜陵磁致伸缩式静力水准仪公司
  8. linux:查看使用中的端口
  9. 强大web打印控件下载 - 2019年最新支持所有浏览器-楚琳打印
  10. python 通达信k线_python日线通达信,通达信 主图默认显示每只股票上市以来所有日线...
  11. java基础-java概述,基本数据类型,基础语法
  12. ACM进阶大一到大三
  13. 【二十二】 H.266/VVC | 选择最优的仿射AMVP候选项 | xEstimateAffineAMVP函数
  14. 2018年·玉伯《从前端技术到体验科技(附演讲视频)》
  15. windows10纯净版系统安装/重装详细教程
  16. CSDN博客专用动态图小程序的推荐---ScreentoGIF教程
  17. mcnpf5输出结果_MCNP入门教程
  18. PHP云任务Q助手Tools程序源码+多功能
  19. 企业管理软件从勤哲excel服务器迁移到奥多odoo的感触
  20. [ 7天学习Python编程,第一天]-----1.4 Python main函数:了解__main__【python舵手】

热门文章

  1. php图片png转APNG,APNG\WEBP图片格式转换器
  2. 社区动态——恭喜海豚调度中国区用户组新晋 9 枚“社群管理员”
  3. java tld文件配置_Java Web应用因tld文件损坏出现的错误
  4. matplotlib作图示例——阻尼衰减曲线、XRD数据绘图、三角函数绘图、超越函数绘图、正态分布随机数(插值多项式)
  5. 大数据风控---风险量化和风险定价
  6. linux swp文件重启,Linux下.swp文件的恢复方法
  7. android电视APP开机自启动,安卓电视、机顶盒如何开机自动启动看电视直播-今日头条...
  8. 怎么还原计算机系统还原,怎么还原以前版本的windows?Win7/Win10系统还原方法
  9. 全方面手把手从0到1带你开发谷歌浏览器插件
  10. Python界面设计之Label