C++热点问题一席谈 (下)
— Bjarne Stroustrup 2005新春专访
荣耀/刘未鹏 译
荣耀: 根据您掌握的资料,模板和泛型编程在业界被广泛采用了吗?还是主要局限于库作者?您认为对于普通程序员(而非库作者)来说,面向对象和泛型编程哪一个更重要?为什么?您认为今后模板和泛型编程应该比今天得到更普遍的应用吗?
Bjarne: “加法和乘法哪个更重要?”大多数情况下这是个愚蠢的问题。同理类推,“面向对象编程和泛型编程哪个更重要?”的问题也毫无意义。关键在于,它们都是基础性的东西,并且是互补的。对于许多问题而言,最佳解决方案往往要求将它们结合运用。
从理论的角度来说,你是在“ad-hoc 多态”和“参数化多态”之间做出选择。而从实现角度来说,则是在“运行期多态”和“编译期多态”之间进行选择。你必须对两者都有所了解,并且恰当地使用它们。和“ad-hoc多态”(在C++中以类继承来表达)相比,“参数化多态”(在C++中以模板来表达)更具规则性,更利于逻辑或抽象思考。这就是为什么它被称为“ad-hoc”、为什么模板代码通常具有更好的性能、以及为什么当个体表达式和语句的性能很重要时我们应该考虑泛型编程的原因之所在。相反,类继承则能够在分离式编译和维护的代码块之间提供更为明晰的接口。
(译注:“ad-hoc”是“专门”的意思。事实上,多态一般分为“ad-hoc多态”和“universal多态”,前者一般指重载,后者一般指“参数化多态”(模板)或“包含多态”(类继承)。你可以从以下链接找到详细的解释:http://www.javaworld.com/javaworld/jw-04-2001/jw-0413-polymorph.html。)
我不能说哪个将会变得更重要,或者哪个应该更重要。那就好像是在加法和乘法之间偏袒某一方一样。但是我怀疑,和没有充分使用类继承和面向对象编程的人相比,有更多的人没有充分使用模板和泛型编程。因此,我更多的时候鼓励人们考虑并使用泛型编程而不是面向对象编程。我确信我们将会看到越来越多的泛型编程应用。泛型编程目前仍然没有被足够的理解和充分的使用。我们接受了近十年的面向对象的耳濡目染,所以我的感觉是面向对象常常被滥用了。值得注意的是,无论如何,泛型编程和面向对象编程这两种技术/风格都比使用一团乱麻似的选择语句、单薄的数据结构以及指针来解决问题要强得多。我们的目标应该是在代码中更为直接和优雅地表达思想,模板和类继承只是为了达到这个目的的工具。
我的感觉是许多“普通的程序员”确实使用了模板,而不仅仅是一些“库设计”方面的精英。当然,我们自己写的模板代码可能要比基础库里的代码简单一些,这很自然,因为对于其它编程技术来说,也存在同样的现象。
荣耀: 我想知道您对模板元编程的看法,您甚至选编了一本模板元编程的书。
Bjarne: 我想我并不愿意把泛型编程和模板元编程区别开来,它们的区别仅仅是在层次和侧重点上。我通常倾向于把这一块统称为泛型编程。事实上我认为模板元编程是一个非常重要的领域,而目前的C++对它的支持却不佳,以至于在生成的代码中它并不能发挥应有的潜力。我认为人们在这一领域所作的许多努力是实验性的,其中许多理应获得成功,因为比起替代方案来,它们能使代码更清晰地表达基础概念,并且具有更好的性能以及可维护性。然而C++98对这些技术的支持却不是很好,所以它们目前还不能成为主流。鉴于此,我在语言的改革方面所作的许多工作都跟泛型编程直接或间接有关。concept(用于分离式检查模板的使用和定义,以及用于更好地重载模板等),更好的初始化,以及更少的不规则性,都是对此有所帮助的努力,同样,用于支持标准库的设施(例如type traits)也会带来帮助。
荣耀: 我个人认为标准C++流库是面向对象和泛型编程结合运用的典范,您赞成这一点吗?对于准备尝试混合使用面向对象和泛型编程技术的程序员,您有什么建议或忠告?
Bjarne: 不,我认为流输入输出流是一个不错的早期尝试。然而,它仅仅是一个非常初步的尝试而已,随着时间的推移而显得过于精致而复杂(正如发生在大多数成功的系统中的那样)。我们现在可以做得更好。在我设计第一个流库时我意识到泛型编程的必要性,但是当时我并没有料到泛型编程最终会变成C++中如此重要的一个组成部分。
荣耀: 我必须得承认我也许太闭目塞听了,我真的没有看到过比标准C++输入输出流更好的面向对象编程和泛型编程的结合应用范例。您能给我一些线索吗?更重要的是,您能告诉我们有哪些是结合运用面向对象编程和泛型编程的最佳场合?谢谢!
Bjarne: 如果你面对的问题既需要某些运行期决议(需要面向对象编程),又具有一些能够从编译期决议中获益的方面(泛型编程的用武之地)的话,那么你就需要将面向对象编程和泛型编程结合起来。例如,面向对象编程的经典例子 — 将一个保存了shape的容器中的所有元素都显示出来就属于这类问题。几十年前我第一次在Simula中看到过这个例子,后来直到遇到了泛型编程,我才看到它的改进实现。考虑以下代码:
void draw_all(vector〈Shape*〉& vs)
{
for (int i=0; i〈vs.size(); ++i) vs[i]-〉draw();
}
我猜想这并不能被看作纯粹的面向对象编程,因为我直接利用了“vs是一个装有Shape*元素的vector”这个事实。毕竟,类型的参数化通常是被认为属于泛型编程的范畴。我们也可以消除这种对静态类型信息的使用(所谓“不纯粹的面向对象编程”):
void draw_all(Object* container)
{
Vector* v = dynamic_cast〈Vector*〉(container);
for (int i=0; i〈v.size(); ++i)
{
Shape* ps = dynamic_cast〈Shape*〉(v[i]);
ps-〉draw();
}
}
但凡鼓励以上这种风格的语言,其语法通常都比较漂亮,然而这个例子却说明了当你把静态类型信息的使用减至最小的时候发生了什么。如今,在C++或其它语言中,仍然有人在使用这种风格。我只是希望他们在错误处理方面有系统化的准备。
在前一个例子中,vector〈Shap*〉依赖于对泛型编程的一个最简单的运用:vector的元素类型被参数化了,而且我们的示例代码正获益于此。在这个方向上我们还可以走得更远,即推而广之到所有标准库容器身上:
template〈class Container〉 void draw_all(Container& cs)
{
for (typename C::iterator p=cs.begin(); p!=cs.end(); ++p)
(*p)-〉draw();
}
例如,这段代码就既可以作用于vector上,又可以作用于list上。编译期决议确保我们不用为这种泛化处理付出任何运行期额外代价。我们还可以通过在draw_all()的使用接口中运用迭代器,从而进行进一步的泛化处理:
template〈class Iter〉 void draw_all(Iter fist, Iter last)
{
for (; first!=last; ++first)
(*first)-〉draw();
}
这就使内建数组类型都得到了支持:
Shape* a[max];
// 向a中填充Shape*类型的元素
draw_all(a,a+max);
我们还可以结合运用标准库算法for_each()和函数适配器mem_fun()来消除显式的循环:
template〈class Iter〉 void draw_all(Iter fist, Iter last)
{
for_each(first, last, mem_fun(&Shape::draw);
}
在这些例子中,我们结合了面向对象(对虚函数draw()的调用以及对类继承体系的假设)和泛型编程(参数化的容器和算法)技术。我看不出如果这两种编程风格(即所谓的“范型”)各自独立运用如何达到同样好的效果。这也是一个简单的“多范型编程”的例子。
我认为在设计和编程技术方面,我们还需要做更多的工作,以便确定出“关于何时采用哪种范型以及如何结合运用它们”的更为具体的规则。为此,我们还需要一个比“多范型编程”更好的名字。
注意,这也是一个关于编译错误信息变得可怕的例子,因为我们并没有显式地表达出我们的假设。例如,我们假设容器里的元素类型为Shape*,然而在代码中,这个假设却相当隐晦。这种情况下我们可以使用约束类(此处为Point_to):
template〈class Iter〉 void draw_all(Iter fist, Iter last)
{
Points_to〈Iter,Shape*〉();
for_each(first, last, mem_fun(&Shape::draw);
}
然而我们又确实很想说明“first和last必须为前向迭代器”:
template〈Forward_iterator〈Shape*〉 Iter〉
void draw_all(Iter fist, Iter last)
{
for_each(first, last, mem_fun(&Shape::draw);
}
这是“concepts”可以大展拳脚的地方之一。
荣耀: 请原谅我重复一个老俗套问题。由于Java和C#今天都已经大获成功,您对Java和C#曾经的看法今天有无改变?我个人认为,与其说Java和C#的成功是语言自身的成功,还不如说是SUN的Java战略和微软的.NET战略的成功。
Bjarne: 在对它们的本质技术优点以及对它们的市场能量的估计上面,我都是正确的。低估市场的影响是不明智的,尤其当它背后有价值上百万美元的“免费”库所支持的时候。Java和C#是不坏的语言,而且SUN及其盟友以及微软及其盟友为其(过分夸张)的宣称提供重大的库和工具的支持。不过,这么说并不意味着我比喜欢C++更喜欢它们,对于要求严苛的应用而言更是如此。
荣耀: 您自觉或自发地使用过GOF描述的设计模式了吗?您对设计模式怎么看?您对Loki库中采用模板技术描述的静态设计模式怎么看?
Bjarne: 我并不喜欢根据特定的具名模式去思考,但我知道并且通常会使用这些在《设计模式》中描述的技术。顺便一提,《设计模式》是一本经典书籍,人们在搜寻最近最好的信息时不应该忘了这本书。其中的许多模式对于好的设计来说非常重要 — 给它们起什么名字倒无所谓。甚至你在TC++PL中也能够找到一些有关它们的运用。要想了解在某个特定领域中对模式有意识且系统的运用,可以参考“深入C++系列”中Schmit和Hunston的两本关于ACE的书。
除了明显的强大能力之外,我认为模式有两大弱点:
它倾向于鼓励“精致的专用术语”,这会阻碍新手的学习。
如果没有具体的“工具”支持,要想把一个思想广泛地传播到应用中是极其困难的。
例如,一个优秀的库本身携带了很多优秀的思想,并允许程序员(和设计者)直接利用这些思想在库中的实现品来工作。而模式只是对某个思想(或一系列互相关联的思想)的尽量一般性的描述,并刻意避免将这些思想作为库实现出来而招致的特殊性。这就导致了这些思想在传播上的问题,以及从代码中如何识别出模式的问题 — 特别是在代码被维护修改过之后。同样,要想从抽象层面上来理解一个模式也是非常困难的。在某个模式的抽象描述之后的实例代码进入我的视野之前,我倾向于对自己的理解持保留态度。我见到过一些人,他们认为自己是在使用某个模式,而实际上做的却是该模式被设计用来避免的事情。这些都说明思想的传授可能会异常困难。
可以让模式更具有可利用性的方式之一是为某些特定的环境提供模式的库的实现。Andrei Alexandrescu的书和他的Loki库可以被看成一次寻求结合高灵活性和高效率(和手写代码一样高效)编程风格的尝试。而模板元编程在大多数情况下都符合这个描述,STL亦然。为了从这种非常一般性的参数化中获益,设计(或编码)抉择必须从运行期转移到编译期,从而程序才更容易在时间或速度上得到优化。遗憾的是,许多编译器都不能很好地把握模板技术所提供的明显的优化机会,这通常是由于编译器过早地扔掉了类型信息,并试图去优化每一片代码,就好像它们是用弱类型风格的C所编写的一样。
荣耀: 您用过UML吗?您对UML怎么看?您认为它对C++程序设计很有用吗?
Bjarne: 我尝试过UML,但并不是为了一些严肃的事情,所以我的看法参考价值不大。对于我最常考虑的设计问题来说,UML不是特别有意义。我发现在设计和记录设计时草图是不可或缺的,但我并不认为把过多的细节加到草图中是个好主意。相比之下,代码更易于表达精确的关系。当然了,这么说并不意味着UML没有用,我所尊敬的一些人认为UML在文档化大型系统时非常重要。
荣耀: 您目前还在写书吗?或者有C++新书写作计划吗?
Bjarne: 我目前正基于我正在讲授的一门新手课程撰写一本面向初学者的编程书。这对于那些具有很少甚至没有编程背景但很想通过努力成为程序员的人应该有所帮助。此前我从未试图为非程序员写书,因为我对完全没有编程经验的人们所知甚少。现在我正教授初学者,这就让我有机会去尝试我的想法,并基于我的教学经验对之不断修改。对于任何瞄准于初学者的东西来说,这样做都是必不可少的。
我的目标是先教给新手最小的一套原则、技术以及语言设施,让他们可以先开始第一个实在的项目。基本上,我打算让那些想要成为职业C++程序员的人由此起步。为了达到这个目的,我一开始的讲授要涵盖很多背景知识,包括数据结构、算法、图以及类设计等,这在传统的教学中是不会很早涉及的。这比我了解到的当前大多数的教学方式更加雄心勃勃。当然,我使用C++作为编程语言,并且我会在讲授中涵盖STL的基础知识。
荣耀: 您能透露一下您的新书何时出版吗?
Bjrane: 我还没有写完呢,而且我还没有跟出版商(Addison-Wesley)协商好进度计划。至少我还需要半年时间来和我的学生们一起精化和锤炼这本书。理论上,我们(Lawrence Petersen是我的合作作者)在明年秋天大概可以完成,如果我们在重审的过程中发现了重大问题的话,那就得等到圣诞节之后才能付梓了。我知道出版商会督促我们早点完成,而我们则会争取更多的时间来检验这本书并采纳反馈。对于这样的一本书,我们是不可以草率对待的。
荣耀: 您对您负责编辑的“深入C++系列”(Addison-Wesley)有何评论?您是否认为其中一些书已经过时了?一些书仅对有限的读者群有作用?您对新近出版的几本书有何评价?还有哪些新成员即将加入这套丛书?我特别想听一听您对《Modern C++ Design》和《C++ Template Metaprogramming》这两本书的看法。
Bjarne: 过时?一些早期的书的确有点过时,但是从总体上来说,这些书都很好地经受住了时间的考验。新的书籍会以稳定的速度加进来。我的猜想是每年会增加三本新书。当然,我希望有更多,但是要想找到质量和实用性足够好的“轻薄洗练”的书并非易事。事实上我很期望能够看到更多精专的书,比如着眼于数值计算的特定方面的书,以及嵌入式系统编程方面的书。人们应该注意的是,这些书中的许多都是以专家或至少有经验的程序员为目标读者的。并且,作为一个专家,应该知道何时使用何种技术。我认为泛型编程和模板元编程是将来的一部分,但那只能算是基础,而STL这样的东西才是当前的主流。你所读到的一些书,像Alexandrescu和Abrahams的那两本书,会为你带来新的概念和可供试验的思想,但是你并不会在正准备部署的系统中立即应用这每一项技术。
荣耀: 您对Boost似乎情有独钟。“深入C++系列”中已经包含了《Boost Graph Library》和《C++ Template Metaprogramming》两本书,我猜将来还会有更多有关Boost的书会加入到这个系列中来,您对Boost怎么看?选编这方面的图书出于什么考虑?
Bjarne: 事实上,我并非根据它们是否是关于Boost的来选择书籍,“深入C++系列”中的大部分书籍都不是关于Boost的。我根据它们是否提供了有关编程技术、原则和概念的有用信息做出选择。恰巧Boost的作者在尝试“根据标准库的精神”来扩展基础库时,使用了有趣的技术去解决有趣的问题而已。
荣耀: 在过去的一年里,只出了有限的几本C++书籍。除了“深入C++系列”中的三本新书外,Addison-Wesley还出版了一本《Imperfect C++》,您对这本书怎么看?(译注:因本书由我们翻译,故有此一问。)另外, 您认为C++0x标准会催生更多的C++新书出版吗?
Bjarne: 人们好像没有以前那样喜欢读书了。今年的C++新书只有寥寥几本,不过已出版的C++书籍已经有很多。“老”书并不一定过时。我认为我自己的书就是很好的例子。《C++程序设计语言》卖得比大多数新近出版的书要好,而《C++语言的设计与演化》则刚刚被翻译成日语。类似地,K&R(译注:《C程序设计语言》)在25年后的今天仍然是最畅销的书!请不要忘了经典。我觉得有一个现象蛮有趣的,我附近的技术书店的Java部分的书几年来第一次比C++部分小了。
我看过《Imperfect C++》的草稿。如果它的页数可以降到原来的一半的话,我非常愿意把它加入到“深入C++系列”中去。该书作者是一个C++热爱者,他希望向开发者展示如何对付C++中诸多不完美之处,以便写出更好的代码,而这些代码在声称为“理想”的语言中更难实现。
我期望C++0x会催生一批新一代的C++书籍。不过,我希望这些新书能够集中于语言对编程风格和设计技术更强固的支持上,而不是简单地列举语言特性。我们已经看到了一大堆令人厌烦的列举语言规则的书。如果孤立开来看,任何语言特性都是无趣的,真正有趣的主题是编程。
荣耀: 由于您正在给C++新手讲解编程课,您愿意给世界上其他C++教师谈些教学经验吗?
Bjarne: 多年来,我一直对普遍的编程教育尤其是C++教学质量感到不愉快。当然,有很多好老师,而且也确实有很多学生变成了很好的程序员,然而,严重误导性的教学和被严重搞迷惑的程序员似乎没有个尽头。大概一年半前,有人建议我为编程新手设计一个全新的编程课程。我有过犹豫,但最终还是答应了,我和我们最具经验的讲师Lawrence Petersen合作,他会弥补我在教授新手方面的经验的不足。我们设计了课程并已讲授了两学期,并且基于我们的讲授方式、客观效果以及反馈(反馈是不可或缺的),不断加以改进。
当然,我并非仅凭个人的主观看法和所知的实验性的途径就一意孤行。事实上,我阅读了大量的口碑好的C++编程入门教材,看看它们到底好在何处。大约有两个星期,我烦躁地走来走去,抱怨着“要是那些玩意就是C++的话连我自己都不会喜欢它!”。我的感觉是许多教材彻底讹传了C++,可怜的学生们!所以,我们首先基于“一个学生成为职业程序员所需要的”知识列出了一个课程大纲,写了讲稿,列出了练习并附以大量的告诫。我们快速的重审材料并根据哪些可行、哪些不可行多次对其进行了调整。是年秋天,我们再次重审了材料,并且将课程的文字印刷成书。这下好多了,但是我们根据学生的反馈再一次调整了许多细节。我们的确越做越好,学生们的反响(包括口头反映和考试成绩)说明了这一点。今春,我们将会再一次讲授这门课,到了夏末时大概就可以进行更大范围的普及推广了。
我们把这种讲授方式称为“深度优先”,因为在课程一开始,我们会给学生介绍许多材料但并不深入细节。我们从第二周开始简单地使用STL,到了第五周学生就知道错误处理策略和类设计。在那之后,我们拓宽他们的知识面,并且让他们把知识应用到一些领域去,譬如文本处理、文件操纵以及绘图等。我认为这是一个充满雄心的教学方式,不过在好的(不一定是极好的)大学里好的学生身上效果不错。
荣耀: 虽然这个问题应该去问Alex Stepanov本人,不过由于最近您们二位结伴前来中国杭州参加一个嵌入式软件系统会议,我顺便想打听一下,Alex目前是否在为C++新标准库忙些什么?
Bjarne: 实际上你真的应该去问Alex本人。他的见解总是很有意思,而且往往出人意料。是的,他也参加了那个大会。在杭州,我们分别做了主题报告并给大学生们做了演讲。让很多人感到惊讶的是,他的报告是着眼于软件业的金融基础的,而绝大多数人所期望的是他能做一个关于STL的高性能应用的演讲。我讲的是有关将抽象映射到机器层结构上的东西,这是C++在嵌入式系统编程中高效应用的基础。我很多主要的想法都可以在C++标准委员会关于性能的技术报告中找到(见我的C++主页上的链接http://www.research.att.com/~bs/C++.html)。
荣耀: 尽管在一些领域C++受到其它语言的挤压,但我相信未来10年内C++仍然是最重要的系统开发语言,您是否赞同这个观点?
Bjarne: 我并不认为C++被“挤压”了多少。IDC的评估数据表明,今天的C++程序员数量大约是十年前的三倍,而且C++仍然比任何其它语言更多地被使用。我认为,更确切的描述应当是这样的:C++在过去的十年里,只吸收了软件开发巨大膨胀的一部分。C++也正在向一些新领域扩张,譬如硬实时的程序设计。我怀疑“最重要的系统开发语言”这一说法很难被量化评估,但勿庸置疑,C++仍将占据非常重要的地位。关于C++应用的多样性,可以看看我列出的应用程序清单:http://www.research.att.com/~bs/applications./html。
荣耀: 还有没有我没有问及而您又希望补充的内容?
Bjarne: 你没有问我容易回答的问题。谢谢。
谢谢您,Bjarne Stroustrup!
没有评论:
发表评论