IEEE Computer “Open Channel”对Bjarne Stroustrup的采访 “再愚钝的脑袋都能认知‘事实’,但提炼出见解则是一门艺术。” — Charles McCabe,《旧金山新闻》 马皓明 翻译 荣耀 指导 标准C++ Computer:1997年11月,ISO通过了C++标准,您出版了《The C++ Programming Language》(Addison Wesley,1997)第三版。在刚过去的几年中C++是如何发展的?ISO标准对于C++社群意味着什么? Stroustrup:C++终于有了一份完整、详尽并且稳定的定义,真是太棒了。这将直接抑或不那么直接地给C++社群带来莫大的好处。各家编译器厂商的关注重心开始从“跟上标准委员会的步伐”向实现的质量议题上转移,自然而然,我们将得到更好的编译器,这种势头方兴未艾。 符合标准的实现可以提供更宽广的公共构建平台,对于工具和库厂商来说,这是一项福利。 这份标准还为程序员提供了一个机会,让他们得以更大胆地试验新技术。对于产品代码而言,过去被认为是“不切实际”的编程风格正倾向于成为“现实的建议”,这样,我们可以写出更灵活、通用、高速而且可维护性更强的代码。 当然,我们需要保持清醒,不能沉湎于“高级”技术的恣意滥用之中。奇迹仍然不会出现,最佳代码仍然是以最直接的方式与合理的设计相匹配的代码。然而,现在是试验并观察“哪种技术适合于特定的人、组织和项目”的时候了。C++编程语言在很大程度上致力于实现这些技术并权衡其利弊。 促使这个进步成为现实的最明显的因素是那些新的主要语言设施:模板、异常、运行时类型信息、名字空间以及新标准库等。某些语言细节的细微修改也不乏其重要性。绝大部分设施好几年前就可用了,不过,如果对可移植性和产品质量有严格要求,我们尚不可依靠这些设施,我们被迫选择“次佳”的方式设计并实现我们的库和应用程序。不过,过了今年不久之后所有主流编译器都将对标准C++提供坚实的支持。 从语言和编程技术的角度来看,对C++最重要的改变包括两方面:持续强调保证静态类型安全和不断增强模板的灵活性。要想优雅高效地从类型安全中获益,功能灵活的模板不可或缺。 新技术的可行性 Computer:您说过,“对于一名经验丰富的C++程序员而言,近几年来没有注意到的往往并非此类新特性的引进,而是使基本的新编程技术切实可行的那些特性之间关系的变化。”您能否给出几个例子证明这个说法适用于标准C++? Stroustrup:重载和模板的规则之组合运用是实现优雅、高效且类型安全的容器的关键技术之一,如果不关心这一点,那它们学起来就很容易了。只有当你在使用容器的过程中,对常常涉及到的基本算法刨根问底时,它们之间的关联才变得显而易见。请考虑清单1a中的程序片断。第一次调用count()时,它计算了数值7在一个整型vector中出现的次数,第二次调用count()时,它计算的是字符串“seven”在一个容纳有string型元素的list容器中出现的次数: // 清单1a: 语言特性间关系的改变可以使基本的新编程技术切实可行:(a)对数值7在一个整型vector中出现次数进行计数的算法,以及对“seven”在一个string型list中出现次数计数的算法,合二为一形成(b)一个可以处理两种调用情况的单独的函数模板。 所有提供重载机制的语言都能以某种方式实现这种效果,而有了模板,我们就可以编写一个单独的函数处理两种调用,就像清单1b所示那样。模板可被分别展开为针对两个调用的最佳代码。此处的count()与标准库里count()功能大体相当,因此用户没必要自己动手编写。这个count()函数自身依靠的是重载机制 — 很明显,整型和string两种类型对相等操作符“operator==”进行了重载(且含义显而易见)。另外,“*”和“++”也被重载,意思是使vector和list的迭代器(iterator)“解引用”和“指向下一个元素”,也就是说,“*”和“++”被赋予了类似于指针操作的语义。 count()的定义展示了C++标准库中容器和算法部分用到的几个关键技术。这些技术很有用,但如果只讲述基本的语言规则,它们一般不被涉及。 请考虑清单2a中稍加改进的count()的变体。count_if()不是针对某个值出现次数进行计数,而是对“使某个判断式(predicate)为真”的元素进行计数。例如,我们可以像清单2b那样数一下“值小于7”的元素数目: // 清单2a: count()示例的改进体(a)对符合一个判断式的元素计数,例如,对值小于7的元素计数(b)。 将此项技术泛化处理,以便可以对付任意类型的任意值进行比较,这需要我们定义一个函数对象(function object),也就是说,一个“实例可以像函数那样被调用”的类,如清单3a所示。其构造函数存储一个“我们希望对其进行比较”的值的引用,如清单3b所示,调用操作符operator()即执行比较。换句话说,f3对vi中小于7的元素进行了计数,并对ls中小于字符串“seven”的元素进行了计数: // 清单3a: 为了处理任意类型的任意值之间的比较,我们定义了一个类,它的实例(函数对象)可以像函数那样被调用,其构造函数接收一个“指向被拿来做比较”的值的引用。 也许值得一提的是,它产生的代码非常高效,尤其是无需执行函数调用或其他类似的速度相对较慢的操作,就能实现比较、获取下一个元素等等。 毫无疑问,这样的代码会让那些非C++程序员感到陌生,甚至对于一个尚未将模板和重载机制新技术熟稔于心的C++程序员也是如此,当然了,展示这个例子的部分原因正在于此。不过,此处的要点在于,这些技术可以让你编写出高效、类型安全的泛型代码。熟悉函数型编程语言的人会注意到,这些技术与那些函数型编程语言倡导的技术类似。 一项项单独的语言特性本质上是枯燥烦人的,它们也可以从优秀的系统构建中分离出来,唯有在编程技术的情境下,它们才显得活泼有趣。 标准库 Computer:标准库是如何制定的?它对C++社群将会带来怎样的影响? Stroustrup:标准库中最新颖有趣的部分当数为容器和算法提供的通用可扩充框架,常被称作STL,最初起源于Alex Stepanov的工作(当时他在Hewlett-Packard实验室工作,目前任职于Silicon Graphics)。 Alex致力于提供恪守通用且高效原则的基础算法,例如,在数据结构中查找元素,对容器的元素进行排序,或者对一个数据结构中某个值出现的次数进行计数。这类算法对于很多计算工作而言都是基础性的。 过去这些年里,Alex使用好几种语言开展这项工作,并且还有几个合作伙伴,特别是David Musser(Rensselaer工学院)、Meng Lee (HP实验室)和Andrew Koenig(AT&T实验室)。我对STL的贡献有限,不过我认为这个贡献很重要:我设计了一个称作mem_fun()的适配器(adapter)。它允许标准算法可被持有多态对象的容器所用,由此,面向对象编程便与标准库泛型编程框架巧妙结合起来。 有点让我惊奇的是,STL符合我前几年为“优秀的C++标准容器集”所制定的一套标准。开始讨论并打造STL大约一年之后,ISO C++委员会接纳了STL作为C++标准库的一个关键部分。我的那套标准包含下列内容:
略微修改和补充之后STL即被采纳为标准,由此避免了让委员会进行这项“可怕的”设计。 显然,一个由兼职志愿者组成的团体(C++标准委员会)不可能为程序员提供所有有意义的库设施,因此,一个关键的问题就是哪些东西应被包含于标准库中,哪些东西有待企业和个人提供。 我们决定以“在分别开发的库之间进行通信之所需”作为标准库应包含内容的指导方针。因此,I/O、string以及各种容器便成为重要的内容。出于历史原因,还包含了C标准库以及一些数值计算方面的设施,但仍缺乏许多具有潜在用途的设施,例如更好地支持日期和货币的设施、正则表达式匹配以及图等。幸运的是,这些设施可以通过商业渠道或共享领域获得。 标准库避免了程序员白手起家重新制造车轮。特别是标准容器允许新手和专家在更高的层次上编程。请考虑清单4所示的简单程序,它简洁地执行了一个简单任务。没有宏,也没有显式内存管理或其他低阶语言设施,也不会遭遇资源限制问题。特别是,如果某个喜欢开玩笑的伙计为这个程序的“name”提供了30000个字符,那么程序将会忠实地为他打印出来,而不会出现越界或其他什么错误: // 清单4: 这个简单的“Hello”程序使用标准库特性简洁地完成了任务。 标准库可以并且应该给C++的教学带来改革。现在可以将C++作为一门更高层次的语言来学习了。只有必需时,并且只有当学习者掌握的语言知识足以为讨论低阶设施提供合适的情境后才应该接触那些东西。因此,标准库将既用作工具,也担当着老师的角色。 复杂性、C++以及OOP Computer:在《The C++ Programming Language》中,您说过,“在任何种类任何规模的项目中,普通程序员在生产力、所开发软件的可维护性、灵活性及质量方面都取得了重大改善。”可有些批评意见认为C++乃至整个OO都过于复杂,为大型系统带来了“检修和维护”问题。作为一名大规模系统C++开发者,这些批评与您的经验能否对得上号? Stroustrup:以我个人经验来看,OOD和OOP能比传统的过程式方法带来更好的代码。它们更具灵活性、扩展性和可维护性,并且无需付出重大的性能代价。我没有足够的强有力证据来反对某种个人观点,但AT&T内部以及其他地方的一些研究可以支持我这一观点。 有两个因素使此问题说不清楚:对于究竟什么是“面向对象”并没有一个统一的认识,另外,鲜有讨论是以充足的经验为基础的。很多“OO麻烦”都源于那些没有足够OO经验的人,他们仅对OO代码应该是什么样子一知半解就着手进行一个野心勃勃的项目。 那么到底OO是什么呢?当然并非所有优秀的程序都是面向对象的,并非所有面向对象程序都是优秀的。假若真是如此,那么“面向对象”就成了“优秀”的同义词,而当你需要做出实际决断的时候,这个概念就成了一个没有丝毫助益的空洞的时髦口语。我倾向于将OOP视同于“大量使用类层次结构和虚函数(某些语言称之为‘方法’)”。这个定义从历史角度看是正确的,因为在Simula时代,类层次结构和虚函数以及伴随它们的设计哲学构成了Simula有别于其他语言的标志。实际上,Simula传下来的这些方面正是Smalltalk大加强调的东西。 基于类层次结构和虚函数的使用而对OO下定义之所以可行,还在于它对OO可能在哪儿获得成功提供了一些指导。你需要找出的是具有层次顺序的概念、可以共享一份实现代码的同一概念的多种变体,以及可以通过一个公共接口进行操控的、类型不必完全相同的多个对象。再加上一些示例和一点儿经验,就构成了一种非常强大的设计方式的基础。 然而,层次结构并非自然而有意义地适用于所有概念,并非概念之间的所有关系都是层次性的,并非将重点集中于对象之上就可完美解决所有问题。比方说,某些问题确实属于基本算法问题。因此,一门通用编程语言应该支持多种思考方式和多种编程风格。这种多样性源自待解决问题和问题解法的多样性。C++支持多种编程风格,因此称之为多范型编程语言比说它是面向对象语言更合适(假如你非得给它贴上一个迷人标签的话)。 符合大部分“优秀” 设计标准(易于理解、灵活、高效)的一个例子是递归下降解析器(recursive descent parser),它是那种传统的过程式代码。另一个例子是STL,它是一个泛型容器和泛型算法库,紧密依赖于传统的过程代码以及参数多态性。 我发现只支持一种编程范型的语言是有局限的。它们通过给程序员套上一件智能紧身衣,或是通过将复杂性从语言转移到应用中以换得语言的简单性(不管是真简单还是假简单)。这对于专用语言是合适的,但对于通用语言则不然。 我常常这样刻画C++的特性:一门偏向于系统编程的通用语言。可以将其视为支持如下功能的“更好的C”:
自然而然,支持多种编程方式比只支持一种方式会带来更大的复杂性。我已注意到,“不同领域各有最合适的设计和编程方式”的观点惹怒了一些人。很明显,我反对“存在一种对所有人所有问题都适用的方法”的观点。那些狂热地笃信“世界原本简单”的人对此问题反应之激烈,超出了我认为在讨论一门编程语言时应有的度。毕竟,编程语言不过是用来构建系统的一个工具而已。 解决此项争端的一个理想途径就是出现这样一门语言:它提供一系列简单的原语,能有效地支持所有优秀的编程风格。人们向这个目标已进行了反复尝试,不过在我看来仍未获得成功。 JAVA与C++ Computer:关于简单性,同样的争执显然可以扩展到Java身上,这可能就是Sun声称Java程序员人数达到70万的原因(与150万名C++程序员相比)。 您坚持认为即便C++无需与C兼容,您也不会设计Java这样的语言。在被问及这个问题上千次之后,您还有什么其他不得不说的话? Stroustrup:这段时间我总被问及有关Java的问题,但我很难作答。如果我说些负面的东西,听上去显得我不够礼貌;如果我说些正面的东西,我就会陷入围绕Java本身以及由Java社群中部分人发出的反C++宣传之商业骗局中。 我鼓励人们根据这两种语言的设计标准来评判它们,而不只是从两者的商业竞争关系来考虑。我在D&E中详细列举了C++的设计标准,而Java从一开始就没有符合这些标准。程序员应该将Java视为多种编程语言中的一种而非一贴万能药。毕竟,C++也不完全符合Java的设计目标。当Java被吹捧成唯我独尊的编程语言时,其缺点和局限性尤显严重。 下边是C++设计标准中的几个条款示例,它们使C++与Java有着显著的区别。不同于Java,C++支持:
我这样设计C++,以便程序员写出既优雅又高效的代码。对于许多应用来说,只要你不想在优雅和效率之间进行折衷,C++仍是最佳选择。 我不清楚人们如何计算“程序员”数量。学生算得上程序员吗?抑或将每一个出货的编译器计算为一名程序员?关于C++提及的人数我是了解的,这个数目是保守而接近实际的。去年售出的C++编译器所得钱款的合理近似值也可据此估算。我不知道Sun关于Java的数字是不是也这么实在。顺便说一下,根据我可以找到的少数几个牢靠数字,比如编译器销量、书籍销量以及C++课程的参加人数等,我估计C++用户的总量每年正以10%到20%的速度增长。 噢,不,我不是一个Java爱好者。我讨厌大肆吹嘘,我讨厌向非程序员推销编程工具,我讨厌专有语言,而我喜欢的编程语言有多种。在技术方面,Java从没有让我像对许多其他语言那样做出“哇,真是优雅!”这样的反应。我认为它提供流行语言特性的方式是有局限的。 Java从C++中借鉴了很多东西,但没有像通常宣称的那么多,也没有达到人们期望的品味和理解力。为了实现它做出的一些关键的承诺,Java必须进一步发展。这种发展可能累及“Java比C++简单”的宣称,不过我猜想这方面的努力会使Java成为一门比现在更好的语言。目前Java似乎正在以相对快的速度增添语言特性和“标准”库。我期待看到Java版本的模板是什么样子。据我所知,Sun还没有从一打上下的此类方言特性中的任何一个受益。 随着Java和Java社群的不断成熟,它有望采取一种明智的“和平共处”哲学,这将使Java成为专业程序员工具箱中多种语言工具中的一种。 工具 Computer:过去几年中,C++系统哪些方面发生了变化?另外,总体上来看,C++舞台以及系统设计方面还有哪些工作有待完成?鉴于C++目前已具有相当的成熟度,您如何评价所有可用的C++开发工具,还要做哪些改进工作? Stroustrup:首先,我希望看到一些充分支持ISO标准的基础工具,例如编译器、调试器、测评器(profilers)、数据库接口、GUI构建工具以及CAD工具等等。比方说,我希望获得STL容器或适当的有类型的对象istream形式的数据库查询结果。工具供应商已经有一个良好的开端,但对于以编译器和其他源代码分析器为基础的工具而言,仍有大量的工作要做。 我列出的那些基本工具就是对“哪些方面发生了变化”这个问题的部分回答。在过去几年中,为数众多的程序员开始依靠精细的工具编写“连接”系统设施的代码。这些工具对于程序员摆脱单调乏味且容易出错的工作必不可少,但这却让程序员(及其老板)冒着被所用工具供应商俘虏的危险。这些工具往往要比一门传统的编程语言复杂得多,而且鲜有这方面的标准。 我鼓励针对这些工具和库制定非专有标准。在一段较短的时间内,比方说10年,很多这类标准将会成为行业标准而非正式的ISO或IEEE标准,不过,关键接口具有良好的规范且广泛可用,对软件工业的健康发展是必需的。随着系统级对象(例如COM和CORBA)的重要性与日俱增,它们与C++的绑定是否具备整洁性和完备的文档说明、使用起来是否简单等诸如此类的方面就尤显重要。不幸的是,这类对每个人都有益的“标准化工作”却被忽视了,因为它不能为任何人在短时间内带来竞争优势。 我宁愿不对可用的C++工具进行专门的“分等定级”。它们都比过去优秀,而且在多数情况下,与其他任何语言的任何工具相比,它们都同样优秀或者胜出一筹。然而,作为一名程序员,我自然期望得到数量更多质量更好的工具。就我个人而言,我希望有更好的C++源代码分析工具。我还希望C++工具厂商们能够从其预算中略微多拿出一些钱用在改进编译器的质量和性能上,而不要过度专注于新奇东西上。对已有编译器进行实际的改进比开发一整套新版C++编译器的花费相对要少。但是,若非大多数用户要求进行“编译器符合标准程度测试”,恐怕厂商们还是会把财力投在更具广告效应的东西上。 未来 Computer:我知道您投身于C++标准化的进程之中,目前您还参与了其他项目吗?您将如何继续进行C++之发展工作? Stroustrup:在已成功完成的这项巨大工程中我历尽周折。ISO C++标准已经完成,《The C++ Programming Language》第三版已呈献在那些严肃的程序员面前,告诉他们标准C++有哪些东西以及如何以最佳方式使用它们,而《The Design and Evolution of C++》则讲述了塑成C++今日面貌的设计决策。还有很多工作要做,但都无需一个全职语言设计者来做了。现在是通过编写代码来享受C++的灵活性和强大威力的时候了,而不是将重点放在那些可能的变化身上。 除了以上所言,我正抓住机会学习一些东西:几门新语言,软件如何运用于大型业务中,并规划一些分布式计算方面的试验。 |