CSDN博客

img hoping

Slashdot对Bjarne Stroustrup的采访

发表于2003/5/27 18:33:00  1520人阅读

注:前段时间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提问)

假如时光可以倒流到您设计C++之初,您会做出哪些改变?为什么? 

Bjarne:我们永远不能回到从前。不过,我想我最明显的失误有:没有在引入多重继承(multiple inheritance)之前引入模板;没能在C++编译器第一版中搭配一个规模较大的库。这两个失误在一定程度上是相关的。 

我没能交付一个库的原因在于我不知道如何编写一个足够好的库。要想使容器高效而且类型安全,我们需要用到模板,而直到1988或1989年我才得到一个支持模板的编译器。但只有模板还是不够的,我们还需要一个针对容器和容器用法的“可以带来安全性”的设计。直到Alex Stepanov带来了STL,我们才有了这样的一个架构。如果你想领教这些有趣的思想和睿智的观点的“激情展现”,可以阅读这篇对Alex的采访。STL是C++标准库的核心,它提供了标准容器和算法,还提供了它们的用法以及“以用户自定义容器和算法进行扩充”的框架。自然而然,《The C++ Programming Language》对此进行了详尽地讨论。

先于模板引入MI所导致的一个后果是怂恿了对类层次结构的进一步滥用。对于某些使用继承机制的较为畸形的应用来说,模板提供了一个简单而高效的替代方案。 

让我说说即使时光倒流我也不会修改的东西 — 兼容性。即便C语言不是最佳兼容人选,我也会选择与某种其他语言兼容。“创新”应该重点着眼于“改进”,那些行得通的东西应该尽可能保持不变。这样的话,人们便可以继续使用现有的工具和技术,也可以在功能业已完备的基础上进行开发,还可以避免“重新发明轮子”,也不用重复教授那些与老内容雷同的“新”内容。因此,C++尽可能地接近于C — 但不能更接近了。 

这个问题的另一面是:你必须处理那些老错误以及兼容性的问题。例如,我认为C的声明语法就是一种失败,不过,在C++中我还是采纳了它。当是时,我考虑的变通方案以及改进方法并不能使情况好转。不过我认为这是一个次要问题,更严重的问题是,随着C的发展演化,C++如何保持与之“接近”。

还有,即便反思过去,我也不会从标准C++中删除任何大的语言特性。添加新的大的语言特性也必须慎重。人们认为需要改变语言才能实现的功能,往往利用库就可以实现。 

我认为C++标准委员会做了很了不起的工作。对一项规范达成共识并非易事。商业竞争的公司之间要达成一致意见,还要让标准传统冲突的国家感到满意。然而,我认为所有时间和努力都没有白费。标准C++比以前任何版本都更加接近我的理想,并且它还伴有一个很棒的标准库。这些时间和努力是不可或缺的 — 除非你想被“事实上的标准”和“专有语言”所支配。我的第三版《TC++PL》讲述了ISO标准C++,目前的C++编译器接近这个标准。

4.为什么没有templated typedefs?(由Venomous Louse提问)

哦,真不可思议,这正是今上午我想找来讨论的家伙! 

typedef templates(或是template typedefs?天啊!)有时候的确能带来便利,如下所示:

template< class T > typedef bool (* func)( const T &r );

……但这似乎并不合法,我不能回想起曾经在《The Design and Evolution of C++》中看到关于此类问题的任何东西。那该怎么办呢? 

Bjarne:我,还有标准委员会,低估了templated typedefs的重要性,我猜想将来会添上它。

实际上,D&E (第357页)已经讨论了templated typedefs,并提出了一项常用的替代技术: 

这种扩充在技术上微不足道,但我并不能确定引入另外一种重命名特性有多么明智。 

在定义一种新类型时,也可以使用派生机制对模板参数进行部分特化(partial specification):

template< class U, class V> class X { /* ... */ };

template< class U> class XX : public X< U,int> { }; 

我猜想,在这种情况下,“我不愿添加无明确实际应用需求的新特性”导致了问题,但我却更常因相反的错误(过于积极地添加特性)而受到指责。可是,在人们学会已有特性的用法之后,基于新经验的新需求又开始涌现。 

顺便说一句,你的例子 — 接受参数T并返回一个bool值的函数,往往可以通过一个函数对象(function object)来表现。而函数对象天生就是模板:

template< class T >

struct Predicate {

    bool operator()(const T &r) { /* ... */ };

};

如此一来,你就不需要typedef了,只要这样写就可以了:

Predicate< Mytype>* p;

函数对象在内联(inline)方面也要优于函数指针,因此使用它们可以编写干净而快速的代码。

通常来说,我推荐那些有着“在C++中它为什么是或不是这样”的疑问的人们阅读《The Design and Evolution of C++》一书。 

5.问题……(由MrHat提问)

我(可能还有我们大多数人)了解您只是通过您对C++语言的创建工作,以及您对编写C++语言ANSI标准的协助工作。 

除了这项工程以外(虽然非常巨大),您日常还做了哪些工作?目前您在进行什么项目?您还有更多的正在进行中的语言定义或标准吗? 

Bjarne:C++相关工作是我日常工作的一大组成部分。虽然C++标准目前处于“维护模式”,但仍然需要投入一些注意力。我做了一些跟库和编程技术相关的工作(今年晚些时候,我将会在《The C++ Report》上发表一篇关于“包装器(wrappers)”的论文)。C++的教育以及“被误导的程序员往往严重误用C++”也让我担心。请参阅我写的《把标准C++当作一门新语言来学习》一文(可从我的论文页面下载)。如你所见,我还进行一些写作、演讲并接受采访。 

在标准委员会里,我将大部分时间花在性能工作组(performance working group)中。成立这个组的目的是为了调查效率低下的根源,并且研究“拯救”劣质代码的实现和编程技术。这项工作的需求场合之一是嵌入式系统。目前的语言使“近于最优化的解决方案”成为可能,但并非所有编译器都能达到那种程度的效率,而且并非每个程序员都通晓编写紧凑而高效的代码的基本技术。 

一如既往,在网上仍然有很多关于语言扩充和新库的讨论,但C++社群仍然没有学会如何充分利用新的便利设施。我想我们可以从优秀的库中获得很多好处。标准库和STL分别从一般角度和特殊角度展示了一些可以做的事情。最终,某些新库将被标准化。 

我正努力提高对分布式计算的认知水平。为此,我阅读了大量的东西,并试验了一些小玩意儿。我关注的内容包括迁移性、可靠性、容错能力以及安全性。这些研究领域也在“导致我设计C++”的研究领域之列,因此从某种意义上说,我回到了“系统(systems)”老家。作为AT&T实验室的一名管理者也要花费一定的时间,但可能不象你想象的那么多,而且它看起来也不像是真正的工作 — 在那个职位,我既不写代码也不写技术文献。 

6.多重继承(由MosesJones提问)

有三个相互关联的问题:

 您认为多重继承是一门真正的OO语言所必需的吗?

在使用多重继承设计系统时,您认为需要避免哪些陷阱(尤其在可维护性方面)?

 您认为有哪些措施,可以简化多重继承的可读性,以减少新手可能做的危险动作?

Bjarne:假如你依赖于静态(编译期)类型检查,你将需要MI。如果没有MI的话,许多程序将被“扭曲”,并且你将不得不过于频繁地使用显式转型操作(explicit type conversion (casting))。我不会声称自己知道什么是“一门真正的OO语言” (即使果真有这么一门语言),但是,假如你本质上必须始终使用显式类型查询(explicit type inquiry)的话,你使用的就不是一门真正的OO语言。 

我并不认为MI是一个特别严重的陷阱来源。MI与单继承以及任何其他强大的语言特性所共有的明显的问题,就是被滥用。我倾向于在简单的聚合(aggregation)中以及实现多个接口时使用MI,例如:

class network_file_error : public network_error, public file_error {

    // ...

};

还有

class interface { // abstract class

    // pure virtual functions

}; 

class base_implementation { // useful functionality

    // data, functions, virtual functions

}; 

class my_implementation : public interface, protected base_implementation {

    // data, functions

    // override some base_implementation functions

    // override all interface functions

};

在后一个例子中,你可以让用户仅仅通过指向接口的指针或引用来访问my_implementation。这使得用户代码可以不依赖于“实现类(implementation class)”,而且系统不会遭遇所谓的“脆弱的基类”这个问题。“实现类”可被更换为一个改进版,而无需重新编译用户代码(除了my_implementation自身)。 

遵循这两种风格可以避免绝大多数问题。当然,你可以在《The C++ Programming Language》第三版和特别版中找到更加广泛的讨论。(如需了解详细情况、样章或评论等,请访问我的个人主页。) 

实际上,关于MI的问题通常意味着提问者已迷失于技术细节之中。对于几乎所有程序来说,使用抽象类、模板、异常以及标准库的所带来益处远远超过其招致的问题。在一门带有继承的静态类型的语言中,MI必不可少,但它并不属于那些应该频繁使用的特性。 

定义具体类(concrete classes)以表示简单的类型,定义抽象类(abstract classes)以表示主要的接口,如果你将重点集中于此,可能会产生一个良好的设计。所以说,完成某个程序可能需要也可能不需要用到MI,但当真正需要的时候,它显然是个优美的解决方案。

7.问题(由Edward Kmett提问)

“constrained templates”有希望被引入C++吗?对程序员来说,当前使用模板是件磨炼毅力的事。我听说在模板引入之初,“constrained genericity”就在标准委员会出现了,有没有什么重新思考那个决定的想法呢?

另一个问题在于“从Eiffel社群获得了大量动力”的“Design by Contract”,我很希望看到它逐步被纳入C++标准,但我怀疑能不能心想事成。 

最后,Bjarne先生,您说过“当可以在C++中使用引用计数(reference counting)时(而不是“假如可以......”),它将是一种可选的技术”,有一本关于面向对象编程语言的书曾引用了这句话(但此刻我在Amazon上贴出ISBN也没能找到这本书)。在引用计数对象技术的前沿有没有取得重大进展呢?自从您的话被引用后,您的观点是否发生了什么变化? 

Bjarne:事实上,我当时说的大意是“当自动垃圾回收机制(auotmatic garbage collection)成为C++的一部分时(而不是“假如自动垃圾回收机制......”),它将是一种可选的技术”。

对于使用频率不太高的资源来说,“引用计数”可能会非常有用,但我并不提倡将它作为一项“跟踪内存”的通用机制。C++拥有良好的内存管理机制(比如构造器、析构器以及标准库容器),但如果你需要更加“自动”的东西,插入(plugging in)一个可用的垃圾回收器(garbage collectors)是个恰当的选择。(可参见《The C++ Programming Language》C.9.1节、我的C++网页或者我的FAQ。) 

顺便说几句,精装特别版《TC++PL》的ISBN是0-201-700735,而平装第三版的ISBN是0-201-889544。 

对模板进行约束且不使其能力受损,这并不像听起来那么简单。详细讨论请参阅D&E。问题之一在于:如果你根据基类来表达模板参数约束的话,将会使你的系统设计向“每个属性(property)都作为一个基类”的畸形风格发展。这极易导致滥用多重继承和间接表达“本来该直接表达”的东西。例如,说“一个类必须拥有一个<<操作符”比“必须继承自‘Printable’(才能拥有一个<<操作符)”更加清晰。表达更直接的代码,也就更加简短,而且生成的代码比“添加一个基类来表达一种约束”的版本品质更佳。 

特化(Specialization)和部分特化(partial specialization)机制为人们提供了许多希望从constraints中获得的表达能力。例如,假如我有一个通用的排序函数模板

template< class Container> void mysort(Container& c);

我还想为vector提供特别的排序算法,那这么写就可以了:

template< class T> void mysort(vector< T>& v);

我更欢迎带有类型错误机制的模板(templates with type errors)所提供的错误信息。其中一些可以通过更好的编译器技术而实现(人们正在为此努力),有些信息则需要某种模板参数约束或检查才能做到,但我不知道如何做到这一点。幸运的是,程序员可以帮忙。考虑我在回答问题1时给出的例子:

template< class Container>

void draw_all(Container& c)

{

    for_each(c.begin(),c.end(),mem_fun(&Shape::draw));

}

如果有一个类型错误,将会在那个相当复杂的for_each()调用决议(resolution)时发生。例如,如果容器内的元素类型为int,那么我们将会得到某种与调用for_each()相关的含糊的错误信息(因为我们不能对int调用Shape::draw())。 

我编写的代码其实如下:

template< class Container>

void draw_all(Container& c)

{

    Shape* p = c.front(); // accept only Shape*s

    for_each(c.begin(),c.end(),mem_fun(&Shape::draw));

}

当无用的变量“p”在初始化时,目前绝大多数编译器都将会引发一个容易理解的错误信息。类似这样的技巧在所有的语言中都很常见,并且已经开发用于所有新颖的构造(constructs)。在“产品代码”中,我可能会这样写:

template< class Container>

void draw_all(Container& c)

{

    typedef typename Container::value_type T;

    assert_equiv< T,Shape*>(); // accept only Shape*s

    for_each(c.begin(),c.end(),mem_fun(&Shape::draw));

}

我使用了一个断言以使它更加清晰。我把assert_equiv()的定义作为一个练习留给读者J 

这引入了对问题的第二部分 — “design by Contract”的使用。既然“design by Contract”是一种设计风格,那么就你可以在C++中使用它。为此,你需要系统地使用断言。我个人倾向于依靠一些“特化的断言模板(specialized assert templates)”(请参阅《TC++PL》第三版)。我也认为这样的一些模板是“作为库的一部分而标准化”的优秀候选者。 

然而,我并不认为语言将会直接支持类似于preconditions和postconditions这样的东西,我也并不认为使用与程序自身本质上相同的语言而编写的preconditions和postconditions是对asserts()的显著改进。还有,我怀疑对类层次结构进行检查(语言支持的conditions已提供)是否值得。通过在目前标准C++中更加系统地使用断言,人们今天可以获得非常显著的好处。

8)一个聪明的问题(哈哈!)(由jd提问)

以软件工程的角度来看,C++是一门基于对象的语言,而不是一门纯粹的面向对象的语言。然而,它仍然以对象概念为中心,而并非以过程(procedures)为中心。 

然而,所有现有的处理器都是过程式的(procedural),并不存在真正意义上的OO CPU。 

您认为OO语言,例如C++,会在硬件级别上导致OO系统的产生吗?或者总是将OO思想限定在抽象层次上,而通过极其复杂的编译器来实现“OO范型”和“过程化范型”之间的翻译,这样是否简单一些? 

Bjarne:在设计C++之前,我的工作内容之一是直接支持“高阶”功能的架构。我得出了一个结论:随着硬件迅速降低价格,速度加快,基于软件的方案要优于高阶硬件。这些优越之处遍及费用、弹性、潜在的用户数量以及“软件可以运行于其上”的计算机的寿命方面(设计并构建一台高阶机器比一个传统机器花费时间要长)。 

在可以预见的将来,“通过编译器将高阶结构映射为低阶硬件基元”的方式仍将占据支配地位。

9.C++的复杂性 VS. OOP的简单性(由hanwen提问)  

首先对本问题中那些可能的“煽风点火”的内容表示歉意J

您怎么解释目前C++的复杂性与面向对象编程大力鼓吹的简单性之间的关系?以下是对本问题的更详细的说明: 

近几年来,C++的实现迅速膨胀,假如我没记错的话,您的著作《TC++PL》内容增加了一倍还多。C++已成为一门规模巨大、极其复杂的语言,我怀疑是否有任何个体(除了您之外)能将所有定义熟稔于心,更不用说团队里的程序员了。 

这意味着在任何项目里要想全面使用C++是不现实的,而且肯定会为一个项目制定严格的“指导方针”来限制哪些特性可以使用,哪些不可以使用。从这个角度来看,“C++使得软件开发更易管理”是值得怀疑的。因此我认为,作为一种意图更高效地编写更好的软件的工具,C++是一个失败者。 

C++的演化被几条“格言”(你不必为你不使用的东西付出代价,与C兼容,等等)所推动,从这个角度来看,C++是成功的,您是否认为这些“格言”需要重新斟酌呢?

Bjarne:煽风点火?我倒认为你非常有礼貌而且很专业 — 你的“编辑器或缓和剂(editor/moderator)”去除了真正过激的成分J 

某种程度的复杂性是不可避免的,我认为“在语言中以直接支持的形式实现强大而常用的技术”是一个优秀的思想(否则我不会这么做J)。你见到过模仿实现“类层次结构”、“参数化的类型”或者“异常”的C代码吗?这类代码倾向于将指针、转型(casts)和宏弄得一团糟。在C++中,此类代码可以干净而简单地表达出来。至关重要的是,这些构造(constructs)有着详细说明的语义(semantics),而不只是一些对代码的意图进行说明的注释。这样一来,复杂性就从代码转移到了语言定义本身(以及编译器)。

你认为在每个项目中没有必要明确地使用C++的所有特性,这是正确的。然而,这并不意味着你需要强加一些限制性的“指导方针”。请问你什么时候在一个项目中明确使用了Unix或NT的所有功能?你希望某个管理者确切地告诉你操作系统的哪些功能可以使用哪些不应该使用吗?它们本来就跟具体的项目毫无关系。

典型的“指导方针”显然来自于“黑暗时代”,它们基于“几年前世界的状态”以及对“什么复杂、什么不复杂”的生疏的假定。那些制定这些“指导方针”的人肯定会说:从总体上看,教育机构在“使学生重点学习C++中有效的关键编程技术”方面做得很差。其后果是导致了“混乱的C风格代码”与“过分臃肿的Smalltalk风格的类层次结构”的搀杂状况。对转型(casts)和宏的大量使用,是非最优化使用C++的普遍共同点。

我见过许多成功的C++项目(远比失败的多)以及对C++的“良好”运用。“良好”一词的意思是:优雅、高效、可靠和可维护。因此,对很多人来讲,C++的确已达到了预期的设计效果。请记住,我只对C++做过一点点明确的、“记录资料良好”(well-documented)的承诺(请参阅《D&E》 和《The C++ Programming Language》)。我不是商业OO骗局的效力者。 

我想我明白“C++的成功使用”与“尊重C++的局限性(施加于‘设计’之上深思熟虑的约束)”以及“积极地使设计思路适应于已提供功能”之间的相互关系。例如,如果你在构建一个层次深的层次结构时,拒绝使用抽象类,并且在每一层都定义很多数据成员,你真的不应该惊讶于编译时间之长、重新编译之频繁以及需要理解“某物在何处定义”的问题之多。类似地,如果你不使用C++的功能,而使用C风格字符串、数组、普通结构(plain structs)以及大量的指向低阶数据结构的指针而搞乱你的代码,你也真的不应该惊讶于碰到C风格的问题而不是获得C++所承诺的好处。

C++语言主要的辅导内容在《TC++PL》第一版中有186页,第二版有282页,第三版则有360页。“增加的部分”目的在于进一步强调编程技术。书页数增加(从第一版327页到最新特别版的1040页)的其它原因是为了描述更多的编程和设计技术以及标准库方面的信息。“特别版”花了363页讲述标准库,这还不包括该书其他部分的标准库应用示例程序。

10)提给Bjarne的问题(由scherrey提问)  

1989年,通过BIX online service,您和Greg Comeau把我带进了C++的世界,当时你们两位开始示范(并最终使我信服)OO并非一时流行的狂热,而且C++语言可以高效地实现OO。《Computer Language》杂志那段时间正在开设“本月语言”特色专栏,当时编程语言有种“来也匆匆、去也匆匆”的趋势。

在我的印象中,您强调以下两个主要目标:a.构建一种语言,使之可以处理那些C语言难以对付的巨型项目;b.在“特性”和“效率”两方面达到一种平衡,因此开发者要为他用不到的特性而有所付出。 

以我个人在种类极其繁多的项目(包括非常“受拘束”的嵌入式系统和大型跨平台的企业级系统)中的C++使用经验来看,毫无疑问,第一个目标已经取得了重大进展,而第二个目标可能已经完全达到了。

然而,最让我失望的是,在系统级的开发中缺少“用C++完全代替老旧平常的C”的能力,而这正是可以通过语言的抽象特性获益最多的领域,同时这也是您声明的语言目标之一。虚函数、继承决议(inheritance resolution)之类的东西的实现技术非常具有“经验主义”性质,因此,在早期我就晓得定义标准ABI(application binary interfaces,应用二进制接口)是不可能的。如今已经过了整整十年时间,一个惊人强大的标准也已经出台,这些实现方面的差异,比基于架构方面的考虑更加具有“人为性”。

如今,开放源代码运动在商业和非商业方面的普及工作都进展得如火如荼。不幸的是,C++不能用来提供可连接的API(linkable API),除非降级到严格受限的基于C的连接,或迫使调用你的库的所有用户和你使用一样的编译器 — 因为不存在关于结构和调用约定的标准表示。

您是否认为现在应该优先考虑ABI的标准化问题了?果真如此,将使用什么机制定义这些接口(事实上的标准 vs.正规的标准,等等)?谁来做这项工作,还有,哪些工作可以促进这项技术的发展?

Bjarne:Ben,你好。

我想我做到了高效、通用以及某种程度上的优雅,然而,我低估了连接器(linker)的问题,我可能也低估了因为与C兼容而滋生的问题。

假如我是一个平台供应商,我在几年之前就会优先考虑实现C++ ABI了,这么一来,当编译器高度符合标准时,所有基于我的平台的厂商就会提供和我一致的实现。据我的了解,这样的工作已经由SUN公司(针对SPARC系统)和一群针对Intel公司即将推出的Merced架构(IA-64,请访问http://reality.sgi.com/dehnert_engr/cxx/)的供应商而展开了。

我将会鼓励所有人 — 尤其是那些编写“意图作为协作成果的一个组成部分”的软件的人来推动实现这种平台ABI标准。假如你在此类人之列,请为在你钟爱的平台上实现C++ ABI而游说。不幸的是,对于如何做到这一点,我无法提供具体建议。 

尽管使用以类或模板表述的高层抽象机制要好得多,但“在系统接口层与C兼容”已经怂恿人们去使用C风格的字符串、数组和结构。他们不但没有使低层工具局限于系统层和类的实现之内,反而让低层结构及其指针弥漫于设计之中。显而易见的后果就是类型错误、“狂野”指针(wild pointers)、数组越界错误和内存泄漏。大量的宏及转型操作(casts)使得代码更加晦涩。眼看着人们陷入一些本可避免的混乱之中,我感到悲哀。

问题的部分原因在于差劲的教育。良好的教育必然是部分解决方案,然而教育也只能起这么大的作用。在我们期望“新手”冲出C子集并使用更强大的技术之前,那些利用现代C++思想而实现的库和工具必须业已遍地开花。

人们应该意识到C++的入门要比C的入门来得容易些。要学习的C++的“最佳初始子集”并不是“C的绝大部分”,与通常情况相比,C++的学习曲线要平滑得多。进一步的讨论请参阅《把标准C++当作一门新语言来学习》一文(可从我的论文网页下载)。

我们一直使用C++编写的应用软件和专门的库来为程序员提供一个高阶的工作环境。标准库的重大意义之一就是为所有人提供了这样的一个榜样。使用标准便利设施(例如string、vector、map以及算法)来编写代码,确实可以改变人们的编程方式以及对编程的思考方式。我希望看到“用于定义和实现标准库”的技术也应用于许多其它领域,从而产生同样的利益 — 无论是在源代码尺寸、类型安全、代码清晰度还是运行时效率方面。 

我认为,试验标准C++中的更高级、更有趣的部分的日子已经来临。举个例子,我新撰写的一份补充材料Standard-Library Exception Handling展示了一种编程风格,它更加显著地违反了“C的普遍常识”,但它可以使代码更加简洁。尽管此文并非为新手而写,但从中可以一瞥标准库的内部运作情况。

当然了,对于“产品代码”我们必须更加谨慎,因为它们与老代码的兼容性的要求更高。然而,我们也不应囿于老旧风格或兼容问题,以至于不敢尝试更现代、更高效的风格。现在有了原生的C++风格( native C++ styles),我们应该使用它们。

一些附加评论

Coward的匿名人士的评论

这真是一些有趣的东西,我将转发给这儿的每一个人。 

我只提一个“负面”的问题:他真的需要那么多次提到他的书吗?好像他从来没有错过一次说“请参阅我的第三版《TC++PL》”之类的话的机会,我往往不信任那些多次提及他们自身或者他们的产品的人。

当然啦,这个问题微不足道,感谢这篇精彩的访谈。 

Bjarne:不妨设想对一位严肃的画家或雕塑家进行一次广播采访。对于这样的一位艺术家来讲,真正重要的是他(或她)的作品,但又不可能通过广播展示这些作品,你将预期获得很多关于作品以及“人们可以去参观这些作品”的博物馆的参考信息。描述性的言辞绝对是不够的。拙劣的艺术家可能会将讨论主题从作品转移到私生活或政治观点上以作为弥补话题,但严肃的艺术家们不会这样做。 

我虽然不是一个艺术家,但对于这样的一个采访来说,同样存在类似的问题。我想展示代码并对问题进行严肃的讨论,但“Q&A (问答)”的方式使我无法做到这一点。我的解决办法就是引用我已经出版的著作和我的个人主页。

毕竟,我已经出版的著作是首要的C++资源。因此,我可以坦然地说,如果你希望了解更多的信息,请访问我的个人主页,阅读《TC++PL》、《D&E》或我的论文。 

C++和科学计算(由J. Chrysostom评论)

作为一名即将毕业的科学计算专业的学生,我有时非常疑惑,为什么C++对数学和科学计算的支持如此有限,而FORTRAN,那个丑陋且难以控制的“野兽”,才是计算数学的唯一“避风港”。 

Bjarne:请看看关于C++数值计算库的链接(例如Blitz++、POOMA、MTL和ROOT),你可能还会跟踪到另外一些C++数值计算方面的网页。 

Davorama的评论

这些问题是如此不寻常,使我无法做出定论,或许你们这些人可以提供一些想法?

您怎么看待模板元编程(template meta programming)?您是否认为它是一种“恩赐”,使得聪明的程序员可以做出像Blitz项目那样酷毙了的东西?抑或是它所带来的便利完全被其“晦涩乃至近乎看不懂”的实现代码给抵消了? 

Bjarne:我确实喜欢一些使用C++进行数值计算方面的东西。常见的思路是模板可被用于消除那些伪临时对象(spurious temporaries)。结果往往是以它们自己的游戏规则击败Fortran,同时还保持了规范的数学记号。在我的个人主页上,你可以看到以下链接:来自LANL的POOMA、来自Warterloo U.的Blitz++、来自Notre dame的MTL。《TC++PL》在数值计算一章有关于这方面基本技术的解释。 

我认为实现代码的复杂难懂无关紧要。实际上,我认为那些代码比另外一些代码(比如说C核心代码)要好懂多了。在效率头等重要时,你不该过分抱怨优化技术有多么难懂。毕竟如果你是一个真实用户的话,你没必要阅读它们的实现代码。

sethg的评论

在回答中似乎有一个普遍的主题,即“那些抱怨是基于过时的信息,请使用遵从标准的新编译器和STL。” 

有没有人为我们这些C++新手维护一个图表,展示目前哪些可用的C++编译器违反了C++标准文件的哪些小节? 

Bjarne:一个大致的回答:目前大厂商提供的编译器都已相当符合标准(我使用它们),例如Borland、GNU、 Metrowerks、 Microsoft以及SGI。 

更详细的信息请参考LANL的列表(在我的C++网页上有对POOMA站点的链接),或者参考一个试图跟踪这类信息的新西兰站点。一些厂商,例如Borland,在它们的网站上公布Plum Hall评估的结果。

还有,你说的对,我认为针对C++而报告的问题有很大比例可以归结为“误解”和“误用”。“有一个现代的C++编译器”是试验我建议的一些技术的先决条件,但是请不要以为一个新编译器本身就会带来多大帮助。你需要真正地改变你的工作方式,不幸的是,现实世界中有太多因素使得这种改变难以实现(遗留代码、缺少学习新技术的时间、多人协作以及过时的风格规则等等)。我没说过这很容易,我只说过这有可能,而且很多人已经取得了成功。 

hanwen的评论 

可能如今标准库可以减轻我的一些苦恼,但我不想再学习这么一个大型C++组件,另外还因为我知道即便如此,C++仍然不够好。它究竟会不会支持反射(reflection)、高序函数(higher order functions)、垃圾回收?。 

这么看来,我发现您的“C++的入门要比C的入门容易一些”的说法是危险的,您在《把标准C++当作一门新语言来学习》中所举出的例子也是如此,因为它们暗示这两门语言中的任意一门都能够或者应该成为一门“入门”语言。 

一门语言,区别对待堆和栈上的非直接对象(non-immediate objects),没有自动垃圾回收机制,允许使用指针,无初始化机制,没有任何“高序(higher-order)”内容,您确实希望人们随着这样一门语言成长吗? 

您教育人们关于C++的内容:和错误的思想斗争,告诉他们哪里适用C++。但是,您未曾告诉过他们哪里不适用C++。C++广为流行,以至人们容易产生这样的误解:这种流行性说明,C++是一门优秀的编程入门语言,或者,C++是一门适用于编写非常巧妙的程序的语言,等等。 

Bjarne:实际上,你说的没错。我认为让人们学习一种“当他们毕业后就再也不可能使用”的语言是不妥当的。Lisp和函数型编程语言(functional language)社群并没有使得自动垃圾回收机制和高序(higher-order)的方方面面都成为主流,尽管它们拥有学院派以及教育机构长达20多年的热情支持。很明显,我认为让人们使用像C这样的低阶语言也非理想选择。

鉴于目前编程及设计教学的糟糕情况,C++可能会取得一个大的进步。当然了,人们也可能无法从C++的抽象机制中获益,而是退化到使用C或C++来编写等价于汇编代码的东西。通过STL,C++社群或许已经使更多的人了解函数型编程(functional programming)技术,或许C++社群已经将此类技术应用于现实问题之上,比以前所有语言所做的总和还要多。函数对象(function objects)不是最具弹性的闭包(closures),但这并不影响这样的事实:人们理解、喜欢并且使用它们。 

《把标准C++当作一门新语言来学习》(在我的论文网页上有链接)一文清晰地陈述了这些方式的作用范围并探讨了原因。看看1988年的C++,它没有模板、异常、RTTI和标准库,它的确是一门不同的语言 — 一门不支持绝大多数现代C++技术的语言。 

如果你需要垃圾回收机制,有很棒的免费的或者商业支持的C++垃圾回收器可供选用(请参考我的C++网页上的链接)。C++垃圾回收器之所以如此高效,原因之一即是C++区别对待配置于栈上的对象和配置于自由存储区的对象。 

姓Coward的匿名人士的评论

啊,正是这个家伙,他一度篡用“C”这个名称来为他的新语言命名,还在AT&T到处说K&R的C语言是“old C”……直到Dennis Ritchie叫他住嘴为止。 

我想他不得不承认,委员会帮他摆平了这门语言早期版本中的大量的问题,而且我也认为他不会宣称目前的C++是完美无暇的。 

鉴于他的成功和知名度,或许我自己有点狂妄自大了J 

Bjarne:我并不真的认为自己极其缺乏谦逊。 

要知道,我与Dennis(虽然不十分密切)还有Brian Kernighan(密切地)共事过十多年。我不认为我盗用过“C”这个名字,即使果真如此,也没有人会向我索赔J 

说C语言是“Old C”的人不是我。为了澄清混淆和避免对Dennis名誉造成伤害,为“C with Classes”取名为 “C++”的正是本人。 

同样,我在委员会工作非常努力(很少有语言设计者为如此“乏味的细节”烦心),同时我也认为委员会做了出色的工作。 

-完-
 

 
 

0 0

相关博文

我的热门文章

img
取 消
img