C++一般性规则

最一般和最重要的C++ 规则与语言的技术方面没太大关系,这些规则几乎都是社会性的,其关注点是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++ 里并不是一个错误。例如,写一个能允许歧义使用的声明本身并不是错误,错的是那些存在歧义性的使用,而不只是这个错误的可能性。按照我的经验,多数“潜在错误”根本不会显现出来,因此推迟错误信息的方式就是不把它给出来。这种推迟也会带来许多方便和灵活性。