STL之父访谈录转载docWord文档格式.docx
《STL之父访谈录转载docWord文档格式.docx》由会员分享,可在线阅读,更多相关《STL之父访谈录转载docWord文档格式.docx(11页珍藏版)》请在冰豆网上搜索。
我在70年代末期在tecton上面所认识到了一个有趣的问题:
被广泛接受的adt观念有着根本性的缺陷.人们通常认为adt的特点是只暴露对象行为特征,而将实现隐藏起来.一项操作的复杂度被认为是与实现相关的属性,所以抽象的时候应予忽略.我则认识到,在考虑一个(抽象)操作时,复杂度(或者至少是一般观念上的复杂度)必须被同时考虑在内.这一点现在已经成了gp的核心理念之一.
例如一个抽象的栈stack类型,仅仅保证你push进去的东西可以随后被pop出来是不够的,同样极端重要的是,不管stack有多大,你的push操作必须能在常数时间内完成.如果我写了一个stack,每push一次就慢一点,那谁都不会用这个烂玩艺.
我们是要把实现和界面分开,但不能完全忽略复杂度.复杂度必须是,而且也确实是横陈于模块的使用者与实现者之间的不成文契约.adt观念的引入是为了允许软件模块相互可替换.但除非另一个模块的操作复杂度与这个模块类似,否则你肯定不愿意实现这种互换.如果我用另外一个模块替换原来的模块,并提供完全相同的接口和行为,但就是复杂度不同,那么用户肯定不高兴.就算我费尽口舌介绍那些抽象实现的优点,他肯定还是不乐意用.复杂度必须被认为是接口的一部分.
1983年左右,我转往纽约布鲁克林技术大学任教.开始研究的是图的算法,主要的合作伙伴是现在ibm的aaronkershenbaum.他在图和网络算法方面是个专家,我使他相信高序(highorder)的思想和gp能够应用在图的算法中.他支持我与他合作开始把这些想法用于实际的网络算法.某些图的算法太复杂了,只进行过理论分析,从来没有实现过.他企图建立一个包含有高序的通用组件的工具箱,这样某些算法就可以实现了.我决定使用lisp语言的一个变种scheme语言来建立这样一个工具箱.我们俩建立了一个巨大的库,展示了各种编程技术.网络算法是首要目标.不久当时还在通用电器的davidmusser加了进来,开发了更多的组件,一个非常大的库.这个库供大学里的本科生使用,但从未商业化.在这项工作中,我了解到副效应是很重要的,不利用副效应,你根本没法进行图操作.你不能每次修改一个端点(vertex)时都在图上兜圈子.所以,当时得到的经验是在实现通用算法时可以把高序技术和副效应结合起来.副效应不总是坏的,只有在被错误使用时才是.
1985年夏,我回到通用电器讲授有关高序程序设计的课程.我展示了在构件复杂算法时这项技术的应用.有一个听课的人叫陈迩,当时是信息系统实验室的主任.他问我是否能用ada语言实现这些技术,形成一个工业强度的库,并表示可以提供支持.我是个穷助教,所以尽管我当时对于ada一无所知,我还是回答"
好的"
.我跟davemusser一起建立这个ada库.这是很重要的一个时期,从象scheme那样的动态类型语言(dynamicallytypedlanguage)转向ada这样的强类型语言,使我认识到了强类型的重要性.谁都知道强类型有助于纠错.我则发现在ada的通用编程中,强类型是获取设计思想的有力工具.它不仅是查错工具,而且是思想工具.这项工作给了我对于组件空间进行正交分解的观念.我认识到,软件组件各自属于不同的类别.oop的狂热支持者认为一切都是对象.但我在ada通用库的工作中认识到,这是不对的.二分查找就不是个对象,它是个算法.此外,我还认识到,通过将组件空间分解到几个不同的方向上,我们可以减少组件的数量,更重要的是,我们可以提供一个设计产品的概念框架.
随后,我在贝尔实验室c++组中得到一份工作,专事库研究.他们问我能不能用c++做类似的事.我那时还不懂c++,但当然,我说我行.可结果我不行,因为1987年时,c++中还没有模板,这玩意儿在通用编程中是个必需品.结果只好用继承来获取通用性,那显然不理想.直到现在c++继承机制也不大用在通用编程中,我们来说说为什么.很多人想用继承实现数据结构和容器类,结果几乎全部一败涂地.c++的继承机制及与之相关的编程风格有着戏剧性的局限.用这种方式进行通用编程,连等于判断这类的小问题都解决不了.如果你以x类作为基类,设计了一个虚函数operater==,接受一个x类对象,并由x派生类y,那么y的operator==是在拿y类对象与x类对象做比较.以动物为例,定义animal类,派生giraffe(长颈鹿)类.定义一个成员函数mate(),实现与另一个哺乳动物的交配操作,返回一个animal对象.现在看看你的派生类giraffe,它当然也有一个mate()方法,结果一个长颈鹿同一个动物交配,返回一个动物对象.这成何体统?
当然,对于c++程序员来说,交配函数没那么重要,可是operator==就很重要了.
对付这种问题,你得使用模板.用模板机制,一切如愿.
尽管没有模板,我还是搞出来一个巨大的算法库,后来成了unixsystemlaboratorystandardcomponentlibrary的一部分.在belllab,我从象andykoenig,bjarnestroustrup(andrewkoenig,前isoc++标准化委员会主席;
bjarnestroustrup,c++之父--译者)这类专家身上学到很多东西.我认识到c/c++的重要,它们的一些成功之处是不能被忽略的.特别是我发现指针是个好东东.我不是说空悬的指针,或是指向栈的指针.我是说指针这个一般观念.地址的观念被广泛使用着.没有指针我们就没法描述并行算法.
我们现在来探讨一下为什么说c是一种伟大的语言.通常人们认为c是编程利器并且获得如此成功,是因为unix是用c写的.我不同意.计算机的体系结构是长时间发展演变的结果,不是哪一个聪明的人创造的.事实上是广大程序员在解决实际问题的过程中提出的要求推动了那些天才提出这些体系.计算机经过多次进化,现在只需要处理字节地址索引的内存,线性地址空间和指针.这个进化结果是对于人们要求解决问题的自然反映.dennisritchie天才的作品c,正反映了演化了30年的计算机的最小模型.c当时并不是什么利器.但是当计算机被用来处理各种问题时,作为最小模型的c成了一种非常强大的语言,在各个领域解决各种问题时都非常高效.这就是c可移植性的奥秘,c是所有计算机的最佳抽象模型,而且这种抽象确确实实是建立在实际的计算机,而不是假想的计算机上的.人们可以比较容易的理解c背后的机器模型,比理解ada和scheme语言背后的机器模型要容易的多.c的成功是因为c做了正确的事,不是因为at&
t的极力鼓吹和unix.
c++的成功是因为bjarnestroustrup以c为出发点来改进c,引入更多的编程技术,但始终保持在c所定义的机器模型框架之内,而不是闭门造车地自己搞出一个新的机器模型来.c的机器模型非常简单.你拥有内存,对象保存在那里面,你又有指向连续内存空间的指针,很好理解.c++保留了这个模型,不过大大扩展了内存中对象的范畴,毕竟c的数据类型太有限了,它允许你建立新的类型结构,但不允许你定义类型方法.这限制了类型系统的能力.c++把c的机器模型扩展为真正类型系统.
1988年我到惠普实验室从事通用库开发工作.但实际上好几年我都是在作磁盘驱动器.很有趣但跟
gp毫不相关.92年我终于回到了gp领域,实验室主任billworley建立了一个算法研究项目,由我
负责.那时候c++已经有模板了.我发现bjarne的模板设计方案是非常天才的.在belllab时,我参
加过有关模班设计的几个早期的讨论,跟bjarne吵得很凶,我认为c++的模板设计应该尽可能向ada的
通用方案看齐.我想可能我吵得太凶了,结果bjarne决定坚决拒绝我的建议.我当时就认识到在c++
中设置模板函数的必要性了,那时候好多人都觉得最好只有模板类.不过我觉得一个模板函数在使用
之前必须先显式实例化,跟ada似的.bjarne死活不听我的,他把模板函数设计成可以用重载机制来
隐式实例化.后来这个特别的技术在我的工作中变得至关重要,我发现它容许我做很多在ada中不可能
的任务.非常高兴bjarne当初没听我的.
您是什么时候第一次构思stl的,最初的目的是什么?
92年那个项目建立时由8个人,渐渐地人越来越少,最后剩下俩,我和李梦,而且李小姐是这个领域的新手.在她的专业研究中编译器是主要工作,不过她接受了gp研究的想法,并且坚信此项研究将带给软件开发一个大变化,要知道那时候有这个信念的认可是寥寥无几.没有她,我可不敢想象我能搞定stl,毕竟stl标着两个人的名字:
stepanov和lee.我们写了一个庞大的库,庞大的代码量,庞大的数据结构组件,函数对象,适配器类,等等.可是虽然有很多代码,却没有文档.我们的工作被认为是一个验证性项目,其目的是搞清楚到底能不能在使算法尽可能通用化的前提下仍然具有很高的效率.我们化了很多时间来比较,结果发现,我们算法不仅最通用,而且要率与手写代码一样高效,这种程序设计风格在性能上是不打折扣的!
这个库在不断成长,但是很难说它是什么时候成为一个"
项目"
的.stl的诞生是好几件事情的机缘巧合才促成的.
什么时候,什么原因促使您决定建议使stl成为ansi/iso标准c++一部分的?
1993年夏,andykoenig跑到斯坦福来讲c++课,我把一些有关的材料给他看,我想他当时确实是很兴奋.他安排我9月到圣何塞给c++标准委员会做一个演讲.我演讲的题目是"
c++程序设计的科学"
讲得很理论化,要点是存在一些c++的基本元素所必须遵循的,有关基本操作的原则.我举了一些例子,比如构造函数,赋值操作,相等操作.作为一种语言,c++没有什么限制.你可以用operator==()来做乘法.但是相等操作就应该是相等操作.它要有自反性,a==a;
它要有对称性,a==b则b==a;
它还要有传递性.作为一个数学公理,相等操作对于其他操作是基本的要素.构造函数和相等操作之间的联系就有公理性的东西在里边.你用拷贝构造函数生成了一个新对象,那么这个对象和原来那个就应该是相等的.c++是没有做强行要求,但是这是我们都必须遵守这个规则.同样的,赋值操作也必须产生相等的对象.我展示了一些基本操作的"
公理"
还讲了一点迭代子(iterator),以及一些通用算法怎样利用迭代子来工作.我觉得那是一个两小时的枯燥演讲,但却非常受欢迎.不过我那时并没有想把这个东西塞在标准里,它毕竟是太过先进的编程技术,大概还不适于出现在现实世界里,恐怕那些做实际工作的人对它没什么兴趣.
我是在9月做这个演讲的,直到次年(1994)月,我都没往ansi标准上动过什么脑筋.1月6日,我收到andykoenig的一封信(他那时是标准文档项目编辑),信中说如果我希望stl成为标准库的一部分,可以在1月25日之前提交一份建议到委员会.我的答复是:
"
andy,你发疯了吗?
他答复道:
不错,是的我发疯了,为什么咱们不疯一次试试看?
当时我们有很多代码,但是没有文档,更没有正式的建议书.李小姐和我每星期工作80小时,终于在期限之前写出一份正式的建议书.当是时也,只有andy一个人知道可能会发生些什么.他是唯一的支持者,在那段日子里他确实提供了很多帮助.我们把建议寄出去了,然后就是等待.在写建议的过程中我们做了很多事.当你把一个东西写下来,特别是想到你写的可能会成为标准,你就会发现设计中的所有纰漏.寄出标准后,我们不得不一段一段重写了库中间的代码,以及几百个组件,一直到3月份圣迭戈会议之前.然后我们又重新修订了建议书,因为在重新写代码的过程中,我们又发现建议书中间的很多瑕疵.
您能描述一下当时委员会里的争论吗?
建议一开始是被支持呢,还是反对?
我当时无法预料会发生些什么.我做了一个报告,反响很好.但当时有许多反对意见.主要的意见是:
这是一份庞大的建议,而且来得太晚,前一次会议上已经做出决议,不在接受任何大的建议.而这个东西是有史以来最大的建议,包括了一大堆新玩艺.投票的结果很有趣,压倒多数的意见认为应对建议进行再考虑,并把投票推迟到下次会议,就是后来众所周知的滑铁卢会议.
bjarnestroustrup成了stl的强有力支持者.很多人都通过建议、更改和修订的方式给予了帮助。
bjarne干脆跑到这来跟我们一起工作了一个礼拜。
andy更是无时无刻的帮助我们。
c++是一种复杂的语言,不是总能搞得清楚确切的含义的。
差不多每天我都要问andy和bjarnec++能不能干这干那。
我得把特殊的荣誉归于andy,是他提出把stl作为c++标准库的一部分;
而bjarne也成了委员会中stl的主要鼓吹者。
其他要感谢的人还有:
mikevilot,标准库小组的负责人;
roguewave公司的nathanmyers(roguewave是bolandc++builder中stl方案的提供商--译者),andersen咨询公司的larrypodmolik。
确实有好多人要致谢。
在圣迭戈提出的stl实际与当时的c++,我们被要求用新的ansi/isoc++语言特性重写stl,这些特性中有一些是尚未实现的。
为了正确使用这些新的、未实现的c++特性,bjarne和andy花了无以计数的时间来帮助我们。
人们希望容器独立于内存模式,这有点过分,因为语言本身并没有包括内存模式。
所以我们得要想出一些机制来抽象内存模式。
在stl的早期版本里,假定容器的容积可以用size_t类型来表示,迭代子之间的距离可以用ptrdiff_t来表示。
现在我们被告知,你为什么不抽象的定义这些类型?
这个要求比较高,连语言本身都没有抽象定义这些类型,而且c/c++数组还不能被这些类型定义所限定。
我们发明了一个机制称作"
allocator"
,封装了内存模式的信息。
这个机制深刻地影响了库中间的每一个组件。
你可能疑惑:
内存模式和算法或者容器类接口有什么关系?
如果你使用size_t这样的东西,你就无法使用t*对象,因为存在不同的指针类型(t*,thuge*,等等)。
这样你就不能使用引用,因为内存模式不同的话,会产成不同的引用类型。
这样就会导致标准库产生庞大的分支。
另外一件重要的事情是我们原先的关联类型数据结构被扩展了。
这比较容易一些,但是最为标准的东西总是很困难的,因为我们做的东西人们要使用很多年。
从容器的观点看,stl做了十分清楚的二分法设计。
所有的容器类被分成两种:
顺序的和关联的,就好像常规的内存和按内容寻址的内存一般。
这些容器的语义十分清楚。
当我到滑铁卢以后,bjarne用了不少时间来安慰我不要太在意成败与否,因为虽然看上去似乎不会成功,但是我们毕竟做到了最好。
我们试过了,所以应该坦然面对。
成功的期望很低。
我们估计大部分的意见将是反对。
但是事实上,确实有一些反对意见,但不占上风。
滑铁卢投票的结果让人大跌眼镜,80%赞成,20%反对。
所有人都预期会有一场恶战,一场大论战。
结果是确实有争论,但投票是压倒性的。
stl对于1994年2月发行的ansi/isoc++工作文件中的类库有何影响?
stl被放进了滑铁卢会议的工作文件里。
stl文档被分解成若干部分,放在了文件的不同部分中。
mikevilot负责此事。
我并没有过多地参与编辑工作,甚至也不是c++委员会的成员。
不过每次有关stl的
建议都由我来考虑。
委员会考虑还是满周到的。
委员会后来又做了一些有关模板机制的改动,哪些影响到了stl?
在stl被接受之前,有两个变化影响到了我们修订stl。
其一是模板类增加了包含模板函数的能力。
stl广泛地使用了这个特性来允许你建立各种容纳容器的容器。
一个单独的构造函数就能让你建立一个能容纳list或其他容器的vector。
还有一个模板构造函数,从迭代子构造容器对象,你可以用一对迭代子当作参数传给它,这对迭代子之间的元素都会被用来构造新的容器类对象。
另一个stl用到的新特性是把模板自身当作模板参数传给模板类。
这项技术被用在刚刚提到的allocator中。
那么stl影响了模板机制吗?
在弗基山谷的会议中,bjarne建议给模板增加一个"
局部特殊化"
(partialspecialization)的特性。
这个特性可以让很多算法和类效率更高,但也会带来代码体积上的问题。
我跟bjarne在这个建议上共同研究了一段时间,这个建议就是为了使stl更高效而提出的。
我们来解释一下什么是"
。
你现在有一个模板函数swap(t&
t&
),用来交换两个参数。
但是当t是某些特殊的类型参数时,你想做一些特殊的事情。
例如对于swap(int&
int&
),你想用一种特别的操作来交换数据。
这一点在没有局部特殊化机制的情况下是不可能的。
有了局部特殊化机制,你可以声明一个模板函数如下:
templateclasstvoidswap(vectort&
vectort&
);
这种形式给vector容器类的swap操作提供了一种特别的办法。
从性能的角度讲,这是非常重要的。
如果你用通用的形式去交换vector,会使用三个赋值操作,vector被复制三次,时间复杂度是线性的。
然而,如果我们有一个局部特殊化的swap版本专门用来交换两个vector,你可以得到一个时间复杂度为常数的,非常快的操作,只要移动vector头部的两个指针就ok。
这能让vector上的sort算法运行得更快。
没有局部特殊化,让某一种特殊的vector,例如vectorint运行得更快的唯一办法是让程序员自己定一个特殊的swap函数,这行得通,但是加重了程序员的负担。
在大部分情况下,局部特殊化机制能够让算法在某些通用类上表现得更高效。
你有最通用的swap,不那么通用的swap,更不通用的swap,完全特殊的swap这么一系列重载的swap,然后你使用局部特殊化,编译器会自动找到最接近的那个swap。
另一个例子copy。
现在我们的copy就是通过迭代子一个一个地拷贝。
使用模板特殊化可以定义一个模板函数:
templateclasstt*copy(t*,t*,t*);
这可以用memcpy高效地拷贝一系列指针来实现,因为是指针拷贝,我们可以不必担心构造对象和析构对象的开销。
这个模板函数可以定义一次,然后供整个库使用,而且用户不必操心。
我们使用局部特殊化处理了一些算法。
这是个重要的改进,据我所知在弗基山谷会议上得到了好评,将来会成为标准的一部分。
(后来的确成了标准的一部分--译者)
除了标准类库外,stl对那一类的应用程序来说最有用处?
我希望stl能够引导大家学习一种新的编程风格:
通用编程。
我相信这种风格适用于任何种类的应用程序。
这种风格就是:
用最通用的方式来写算法和数据结构。
这些结构所要求的语义特性应该能够被清楚地归类和分类,而这些归类分类的原则应该是任何对象都能满足的。
理解和发展这种技术还要很长时间,stl不过是这个过程的起点。
我们最终会对通用的组件有一个标准的分类,这些组件具有精心定义的接口和复杂度。
程序员们将不必在微观层次上编程。
你再也不用去写一个二分查找算法。
就是在现在,stl也已经提供了好几个通用的二分查找算法,凡是能用二分查找算法的场合,都可以使用这些算法。
算法所要求的前提条件很少:
你只要在代码里使用它。
我希望所有的组件都能有这么一天。
我们会有一个标准的分类,人们不用再重复这些工作。
这就是douglasmcilroy的梦想,他在1969年关于"
构件工厂"
的那篇著名文章中所提出来的东西。
stl就是这种"
的一个范例。
当然,还需要有主流的力量介入这种技术的发展之中,光靠研究机构不行,工业界应该想程序员提供组件和工具,帮助他们找到所需的组件,把组件粘合到一起,然后确定复杂度是否达到预期。
stl没有实现一个持久化(persistent)对象容器模型。
map和multimap似乎是比较好的候选者,它们可以把对象按索引存入持久对象数据库。
您在此方向上做了什么工作吗,或者对这类实现有何评论?
很多人都注意到这个问题。
stl没实现持久化是有理由的。
stl在当时已经是能被接受的最巨大的库了。
再大一点的话,我认为委员会肯定不会接受。
当然持久化是确实是一些人提出的问题。
在设计stl,特别是设计allocator时,bjarne认为这个封装了内存模式的组件可以用来封装持久性内存模式。
bjarne的洞察秋毫非常的重要和有趣,好几个对象数据库公司正在盯着这项技术。
1994年10月我参加了objectdatabasemanagementgroup的一个会议,我做了一个关于演说。
他们非常感兴趣,想让他们正在形成中的组件库的接口与stl一致,但不包括allocator在内。
不过该集团的某些成员仔细分析了allocator是否能够被用来实现持久化。
我希望与stl接口一致的组件对象持久化方案能在接下来的一年里出现。
set,multiset,map和multimap是用红黑树实现的,您试过用其他的结构,比如b*树来实现吗?
我不认为b*适用于内存中的数据结构,不过当然这件事还是应该去做的。
应该对许多其他的数据结构,比如跳表(skiplist)、伸展树(splaytree)、半平衡树(half-balancedtree)等,也实现stl容器的标准接口。
应该做这样的研究工作,因为stl提供了一个很好的框架,可以用来比较这些结构的性能。
结口是固定的,基本的复杂度是固定的,现在我们就可一个对各种数据结构进行很有意义的比较了。
在数据结构领域里有很多人用各种各样的接口来实现不同的数据结构,我希望他们能用stl框架来把这些数据结构变成通用的。
(译者注:
上面所提到的各种数据结构我以为大多并非急需,而一个stl没有提供而又是真正重要的数据结构是哈希结构。
后来在stepanov和mattaustern等人的sgi*stl中增补了hashset,hashmap和hashtable三种容器,使得这个stl实现才比较完满。
众所周知,红黑树的时间复杂度为o(logn),而理想hash结构为o
(1)。
当然,如果实现了持久