注:前段时间myan在csdn上贴了一个对各大语言,以及oo和模块化的评价的文章。下面这篇对c++之父的采访中,bjarne stroustrup谈到了自己的看法。通过大师们思维的碰撞,我们能从中学到什么呢?
slashdot对bjarne stroustrup的采访
荣耀 马皓明 译
1.oo的发展已呈颓势了吗?(由rambone提问)
20多年过去了,显而易见,面向对象编程并非包治百病的“万能药”,请问您对oo范型(oo paradigm)的未来有什么看法?您认为有什么别的范型对它形成了挑战?
bjarne:哦,20多年以前我就明白oop的确不是“万能药”,这也正是c++支持好几种设计和编程风格的原因。
假如你喜欢罗里罗嗦的词语,你可以说c++是一门“multi-paradigm language”,不过,简单地说c++是一门“oopl”的确是不准确的。为此,我撰写了一篇论文《为什么c++不仅仅是一门面向对象的编程语言》(可以到我的“论文页面”下载)。我在oopsla(object oriented programming systems,languages, and applications)会议上展示了它,并且它幸运地得以存留。
在《the c++ programming language》第一版中,我并没有使用“面向对象编程”这个术语,因为我不想对那些大肆的宣传推波助澜。oop的问题之一正在于肆无忌惮的人们宣扬它是一贴“万能药”。什么东西一吹过头了,最终必然会导致对它的失望。
换句话说,虽然ood和oop是我钟爱的设计和编程方式,但它们并非对所有程序所有细节来说都是恰当的风格。一些优秀的抽象例子并没有使用类层次结构,同样也被极佳地表达了出来。试图将类层次结构尤其是单根层次结构应用到所有问题之上,很可能会导致的确畸形的程序。
我大量使用了简单的数据抽象(没有继承关系的类)和泛型编程(模板和参数化类型的算法)。然而,我并不将此举视作“泛型挑战oop”,它们是互补的技术。“寻找适合目标问题的设计方案”以及“在代码中使用最佳语言结构来表达设计方案”,永远才是关键。
组合运用各种风格可以写出非常优雅的代码。例如:
void draw_all(list< shape*>& lst)
{
for_each(lst.begin(),lst.end(),mem_fun(&shape::draw));
}
在这段代码里,list和for_each()是c++标准库中泛型设施的例子,它们被用来调用一个传统类层次结构中的一个基类的一个虚函数。
你甚至可以编写一个适用于“任何具有shape*元素”的标准容器的draw_all()版本:
template< class container>
void draw_all(container& c)
{
for_each(c.begin(),c.end(),mem_fun(&shape::draw));
}
我选择这个例子来唤醒那些依旧“生活在黑暗时代”并且认为c++不过是对c做了一些无趣的扩充的人们。
当然,这个版本的draw_all()所产生的代码与列表遍历代码(list traversal code)具有同等的效率,用于调用shape::draw()的多态机制可以归结为“通过一个函数指针数组来调用每一个函数”,此一机制已经应用于设备驱动程序的调用之中。
每一年都会有新“范型”被吹捧出来,但它们多数都不具有基础性的意义,而且大都缺乏新意。最有意思的可能是那些基于“组件”的模型,例如com和corba。但我不能确定这些东西能否构成一种“新范型”,抑或它们不过是一套新的系统级的“构建模块”而已 — 我们难道说unix进程(processes)是一种新范型吗?总之,我对此类组件的基本观点是:它们仍然不能和编程语言(如c++)以及形形色色的“传统”的编程范型(如oop和泛型编程)完美地集成。
2.使用c++进行系统编程(systems programming)(由ajs提问)
长期以来,c一直是unix世界系统编程语言,而且仍将如此。我知道您不愿意对语言进行比较,所以我只问问您,是否有什么本质的原因,使得c++无法成为一门优秀的系统编程语言,或者说,不能引起c系统程序员的兴趣?
bjarne:让“老家伙”学习新技术是件难事儿。
unix是25年前编写的(我第一次用它是在1973年)。它所有的接口都定义为c函数调用、数组以及结构(structs)。到c++可用的时候,c语言几乎唯我独尊的传统已经持续了10多年了。
unix程序员没有什么合理的借口避免使用c++,相反,倒是有一些很好的理由去使用它。然而,有数不清的“不使用c++”的借口被提了出来,让我来列出几条:
c++速度慢
c++产生“臃肿的”代码
缺乏优秀的c++编译器
c++太复杂
c++没有为系统程序员提供很多便利条件
c++的高级特性不适于系统编程
c++代码不可移植
c++没有abi(application binary interfaces,应用二进制接口)
这些借口要么荒谬至极,要么很大程度上无关紧要。其中一些原因对于某些系统(比如缺少合适的c++工具的微型嵌入式系统)来说有一点道理,但对于unix/linux绝非如此,我将简要地解释一下原因。当然了,完整的讨论需要几百页纸头,因为单单举反例是不可能的,换句话说,想证明“对某人来讲没有什么问题不可以解决”是不可能的。
对于同样的程序,c++可以产生与c同样优秀的代码,尽管去试吧!
c++就是被设计用来做那些事情的,当前交付的编译器都遵守这个承诺。
当然了,使用任何语言你都能写出糟糕的程序。c++虽然是一个强大的工具,但对它的误用也会产生明显畸形而臃肿的代码。这可以和那些拙劣的程序员使用c语言编写的“传统的意大利面条”相“媲美”。要注意,一名优秀的c程序员并不会自动成为一名优秀的c++程序员。许多问题都是这么冒出来的:某些优秀的c程序员总是想当然地以为自己随便学点c++语言特性,一周之内摇身一变就成了c++高手。
在一周之内c程序员的确可以从c++中获益,但这仅局限于c++功能的一个子集和库。
c++支持许多威力强大的技术,而这些技术在c中顶多只有微弱的支持,它们学起来是需要时间的。c程序员或许还清楚地记得成为“大师级”c程序员花了他们多少时间。为什么成为一名“大师级”的c++程序员花费的时间就要短一些?我想不出任何理由。
当前一代的c++编译器在符合标准方面远胜于几年前的编译器,但优化器(optimizers)却与c共享。这可能会导致问题,因为它排除了某些不适合c语言的有意义的优化,不过,至少与c共享编译器的大部分,却也能使那些怀疑c++是否可以产生与c等价的代码的人们打消疑虑。
我将在对问题9的回答中探讨语言复杂性的根源。在此,我想指出的是,c++的许多功能可以直接帮助人们编写出高效、可靠且可维护的代码,假如你不去用这些功能,你很可能最终需要利用低阶语言结构模拟实现它们。
甚至那些“新的”或者“高级的”c++特性,例如模板、异常以及运行时类型信息(rtti),也遵从“零开销”设计原则。假如你需要这些特性,在一个现代的编译器中使用它们比使用c语言来仿造这些功能更加高效(在运行时间和内存空间两方面皆然)。我还没有听说任何c++语言功能在某些系统或嵌入式应用中“没有用”或“负担不起”。
当然了,如果你不需要某种特性(rtti往往用不着),或某种特性在特定情境下不适用(我可以想出一些不适用异常(exceptions)的程序),你不用它们就是了。“零开销”原则允许你做出这样的决策。这和“在一个给定的应用中不使用某种不适当的c语言特性(例如在一些嵌入式系统中禁止使用malloc())”没什么两样。
c++具有和c一样的移植性。两种情况下,你都需要对“系统依赖性”进行封装以便移植。大型程序可以在unix平台之间而移植,某些程序还可以跨越其他平台进行移植。这些技术广为人知,而且当它们应用情况良好时,c++在“将无痛移植的系统概念进行正式化”的方面,甚至具有一个优势。要想看一个例子,你可以参考“定义ace平台”的c++库(我的c++网页上有链接)。
c++缺乏二进制接口(abi),最难解决的技术问题可能非此莫属了。c也没有abi,但在绝大多数(或者全部?)unix平台上,都有一个“占支配地位”的编译器,其他编译器要想“有用”,都必须遵守它的调用约定和结构布局规则。在c++中,则有更多的“变数”,例如虚函数表(virtual function table)的布局,因此没有任何厂商被批准为c++创建“所有竞争厂商都必须遵守”的abi。同样道理,以前连接(link)由两台不同pc上的c编译器所产生的代码也是不可能的,所以通常也不可能连接两个不同unix c++编译器产生的代码 — 除非有兼容性开关。
目前的解决方案通常是将“使用单一的编译器”与“提供c级别的接口”二者结合使用,这并不理想,请参考我对问题10的回答。
也就是说,我认为最主要的问题在于“教育”。对于“c++是什么”以及“它能用来做什么”,很多人的看法严重错误。错误的看法往往累积成学习的严重障碍。现在的c++与1985年的第一版相比已经大相径庭,c++的iso标准于1998年被批准。对我来说,目前的编译器已足够接近标准。我可以将那些利用较新功能的程序,在编译器之间进行移植,以便在多种平台上进行性能测试,这其中标准库功不可没。
3.您会有什么不同的设计?(由spiralx提问)