hangwire 阅读(830) 评论(1)

荣耀/刘未鹏 译

荣耀: Herb Sutter和Stan Lippman目前正在微软主持C++/CLI的设计工作,意图将动态的、基于组件的.NET编程模型和ISO C++集成在一起。您对此有何评价?您认为C++需要.NET吗?您认为C++/CLI会取得成功吗?

Bjarne: 不,C++根本不需要.NET,C++只需要最小限度的运行时支持,用于new/delete、异常处理以及RTTI等,而且仅当你使用这些特性时才需要。C++程序通常可以使用每一分可用的资源,在硬件上直接跑。C++的这些能力使其非常适合于系统级编程以及嵌入式系统任务。当然,也有些C++应用需要.NET,比如那些为了和微软.NET框架和服务紧密集成而专门设计的应用。然而,C++语言和标准库的宗旨是远离这些平台相关性的纠缠。另一方面,许多.NET设施都依赖于C++,因为除了C++之外,再也找不到更通用、更高效的语言来很好地完成这个任务,从这个意义上说,.NET需要C++。

从“很多人将会使用它”这个意义上来说,C++/CLI是会成功的。使用.NET CLI,开发者选择甚少,而C++则是最佳选择之一,而且很明显在Windows上也是,因为微软给予C++最好的支持。话虽如此,我仍然倾向于在设计系统时保持良好的移植性,而将对平台相关或专有特性的使用限制在特定的代码块中,并使用以ISO标准C++所表达的接口去访问它们。

荣耀: 尽管我现在相信这是一个毫无意义的问题,不过我想我最好还是澄清一下。当我说“C++需要.NET吗?”,我的意思是想问“我们需要.NET来使C++更普及吗?”。这就好比问“世界和平需要美国吗?”,或者,“我们需要美国来维护世界和平吗?”。当然了,我们都不喜欢讨论政治性话题,也许这个比方很不合适。

Bjarne: 政治关乎可行性。从这个意义上来说,我们必须考虑政治,而你的问题当然也是合理的。鉴于微软在软件领域的地位以及它对.NET强大而完全的支持(将.NET的系统接口以CLI来表达),.NET的地位会变得很重要。要想在微软的世界里玩得转的话,C++必须很好地绑定到.NET。事实上这种绑定(C++/CLI)已经建立了,微软还为之申请了ECMA标准。.NET跟我理想中的尚有些差距,而C++/CLI如果让我来设计的话可能也不会是这个样子,然而不可否认的是,C++/CLI在.NET上的确是能力非常强的语言,也是迄今为止为.NET设计的语言中能力最强的。如果微软未考虑将C++作为.NET上的关键语言之一,或者.NET平台上的应用创建者没有坚持对C++提供第一流支持的话,情况会糟很多。

所以,为了能够在微软的世界里流行,C++需要一个良好的CLI绑定。我仍然鼓励人们将C++/CLI仅仅视作一个绑定物。这就是说,把对C++/CLI特性的使用隔离到一些特定的区域里,并且通过ISO标准C++设施去访问它们。C++/CLI的一些设施使其成为一门具有吸引力的语言,然而和标准C++却相去甚远,所以如果你在代码中到处使用这些C++/CLI设施的话,那么你将失去平台无关性,并有损失性能优势的潜在危险。

荣耀: 鉴于Herb Sutter的双重身份:ISO C++标准委员会主席和微软软件架构师,C++/CLI对C++0x标准会产生什么样的影响?这种可能的影响是您希望看到的吗?

Bjarne: C++/CLI会在一些领域对C++产生影响,因为将会有许多人使用它。显而易见,在使用中人们将会建议加入一些新的设施,以便和ISO C++平滑地互操作。在确保ISO C++平台中立的前提下,这些建议会依据其优点而被评估。我并不认为Herb的双重身份会带来任何负面影响。请注意,ISO委员会的会议召集人的角色属于管理方面的。我认为微软将Herb的才能贡献到标准化进程中是一个积极的信号。一如往常,微软在实践着其“拥抱并扩展”策略,但至少他们拥抱的是ISO C++而非某种C++方言。

荣耀: C++0x标准大概可于哪一年颁布?目前标准化工作进展如何?我们在这个新标准中预期可以看到哪些新特性?

Bjarne: 我希望三、四年内能够颁布,不过目前我们还没有一个明确的进度表。

我们打算在语言的扩展上持保守态度,并在与C++98的兼容性方面仔细斟酌。改进的关键可能会落在对泛型编程更好的支持以及对新手更易学习上。我们期望一个关于模板实参的类型系统“concepts”能成为泛型编程的基石。

在标准库的扩展方面我们打算胆子更大一些。新标准库技术报告也许会使你对它的发展方向有一些认识。我们的关键目标是使标准库成为一个更广泛的系统编程平台!

荣耀: 一个冒昧的问题。为什么C++标准委员会主席是Herb Sutter而不是您?我记得您是进化工作组主席,我们都很有兴趣知道您在目前标准化过程中具体从事什么工作。

Bjarne: 我并不想担当会议召集人的职务。Herb和他的前任们在那个职位上比我所能做到的要出色得多。会议召集人主要负责管理性和组织性的事务。我的(非正式的)角色在于努力维持语言一致性的方向。我是语言进化工作组的主席,这个工作组负责处理所有语言扩展方面的提议。这个职位意味着绝大部分定义语言改变的文本都是我写出来的,而这些文本最终形成了标准文档。

作为进化工作组的主席,目前我正致力于三件事情:“concept”,改进的初始化设施,以及对C++新手的更好支持。你可以从我的主页上以下链接看到人们对标准下一个修订版(即“C++0x”)建议的期望特性列表:http://www.research.att.com/~bs/C++.html。简单的数一下你就会发现这些特性根本不能全部塞到标准中去,所以,我们需要一些优先级上的考虑。此外,我们还需要把注意力集中到一些相互关的问题上,而不是每次单独处理这些个体提议。如果把每个特性都单独考虑的话,你无论如何也不能得到一个内在一致且易教学的语言。正如语言本身一样,特性必须是为解决问题而设计的,而不仅仅是一个一个地“看上去很美”。

让我扼要介绍一下我眼下正以高优先级进行的三个语言扩展:concepts,初始化,以及“消除一些令人不愉快的瑕疵”:

在《C++语言的设计与演化》(D&E)中对模板的讨论部分,我花了整整三页来讨论模板实参的约束。很明显,我觉得它需要一个更好的解决方案。在使用模板(例如标准库算法)时哪怕出一丁点儿差错都可能招致极其“壮观”而无用的错误信息。问题在于,模板代码对其模板实参的“期望”是隐式的。让我们考虑一下find_if():

template<class In, class Pred>
In find_if(In first, In last, Pred pred)
{
    while (first!=last && !pred(*first)) ++first;
    return first;
}

这里,我们对类型In和Predicate作了若干假设。从代码中我们可以看出,In必须支持!=、*和++,并且这些操作符还必须具有恰当的语义。另外,我们必须能够拷贝In类型的对象作为实参和返回值。类似地还可以看出,我们可以“以*作用在In对象上所返回的值”作为实参来调用Pred,并且把“!”运用到该调用返回的结果上,从而得到一个可以在语义上看成是布尔值的东西。然而,这些约束在代码中都是隐式表达的。标准库为前向迭代器(此处为In)以及谓词(此处为Pred)小心翼翼地记录了各自要求的条件,但编译器可不会阅读文档!试试以下错误的代码,看看你的编译器会有什么反应:

find_if(1,5,3.14); // 错误!

我以前的想法提供了一个不完备、但相当高效的解决方案,即使用一个构造函数来检查对于模板实参的假定条件(见D&E 15.4.2),这个解决方案现在得到了广泛运用,被称为“concepts(概念)”或“constraints classes(约束类)”。你可以从我的主页上的技术FAQ中找到一些例子:http://www.research.att.com/~bs/bs_faq2.html#constraints

然而,我们真正想要告诉编译器的是“我们期望模板实参是什么样子的”或者“我们期望模板实参满足哪些要求”,例如:

template<Forward_iterator In, Predicate Pred>
In find_if(In first, In last, Pred pred);

假设我们可以表达Forward_iterator和Predicate是什么,编译器就能够在不用查看find_if()定义的情况下检查对它的调用是否正确。这里我们所要做的就是为模板实参构建一个类型系统。在现代C++中,这种“类型的类型”被称为“concepts”。有多种方式可以用于表达concepts,眼下我们暂且把它们看成一些受到直接的语言支持并具有优雅语法的“约束类”。一个concept表明了一个类型必须提供哪些能力,但并不强制规定它们如何提供这些能力。理想的concept(例如<Forward_iterator In>)应该非常类似于数学抽象(对于任意的In,它必须可被递增(++)、解引用(*)以及拷贝),就像原来的形式“<class T>”从数学上来说是“针对所有的类型T”那样。

这样一来,在仅仅给出find_if()的声明的情况下,我们可以写:

int x = find_if(1,2,Less_than<int>(7));

这将会失败,因为int并不支持解引用(*)。换句话说,这个调用将不能通过编译,因为int并不是一个Forward_iterator。很重要的一点是,这将会使编译器更容易在该调用首次被看到的那一点上报告用户所犯的错误。

遗憾的是,仅仅知道iterator实参是Forward_iterator以及predicate实参是Predicate还不足以保证对find_if()的调用能够成功编译。这两个实参类型之间是有着交互作用的。说得详细一点就是,predicate所接受的实参是一个被解引用的iterator (pred(*first))。我们的目标在于对模板进行“和调用相分离”的完全检查以及无需查看模板定义就能对每次调用进行的完全检查,所以,concept必须具有足够强的表达力,以便处理这种模板实参之间的交互关系。方式之一是对concept本身也进行参数化,就像模板本身的参数化那样。例如:

template<Value_type T,
Forward_iterator<T> In,  // 对一个T序列进行迭代
Predicate<bool,T> Pred>  // 接受一个T并返回一个bool
In find_if(In first, In last, Pred pred);

在这儿,我们要求Forward_iterator必须指向一个T类型的元素,而该元素的类型必须和Predicate的实参类型一样(译注:实际上只要类型兼容即可)。

这方面的工作正在进行中。你可以从C++委员会的文件、学术文献以及有关C++0x的讨论中找到这方面更多的信息,在这里我没有太多的时间或地方进行更详细地阐述。“concept”的目标是提供模板使用和模板定义的完美的分离式检查,同时不引入任何运行期负担(译注:在C#的所谓的泛型中,concept只不过是间接函数调用的语法糖而已,运行期额外负担仍然存在。)以及不必要的concept耦合(译注:在C#所谓的泛型中,concept要求模板实参继承自一个公共基类,因此耦合仍然存在。)。

换句话说,我们想要把静态类型检查的好处引入到C++中的高度抽象的层面上去,同时不损及目前的模板技术所提供的灵活性和效率。

C++的基本思想之一是“对用户定义类型提供和内建类型一样良好的支持”(见D&E4.4)。但考虑下面这个例子:

double vd[ ] = { 1.2, 2.3, 3.4, 4.5, 5.6 };
vector<double> v(vd, vd+5);

我们可以直接使用“初始化列表”来直接初始化数组,而对于vector,最好的情况是我们可以先创建一个内建数组然后再用它来初始化vector。如果只有很少的几个初始化值,我可能会倾向于使用push_back()以避免将初始值的数目显式“写死”在代码中(上面例子中的初值是5个):

vector<double> v;
v.push_back(1.2);
v.push_back(2.3);
v.push_back(3.4);
v.push_back(4.5);
v.push_back(5.6);

我想任何人都不会认为这两种解决方案有任何“优雅”可言。要想得到可维护性更好的代码并且让vector比内建(具有固有的危险性)数组更“讨人喜欢”的话,我们需要这样的能力:

vector<double> v = { 1.2, 2.3, 3.4, 4.5, 5.6 }; 

或者:

vector<double> v ({ 1.2, 2.3, 3.4, 4.5, 5.6 });

由于实参传递是依据初始化来定义的,因此这对接受vector为参数的函数同样奏效:

void f(const vector<double>& r);
// …
f({ 1.2, 2.3, 3.4, 4.5, 5.6 });

(译注:这里即是说,实参传递和初始化的语义是一样的,例如:

void f(T a);
f(x); 

这里“把x作为实参传递给a”的过程等同于

T a = x;

这是个初始化表达式。)

我相信这种初始化器(initializers)的一般形式将会成为C++0x的一部分,这不过是将成为对构造函数进行全面检修的一部分,因为人们已经发现了有关构造函数的不少弱点,这些弱点看起来可以通过对构造函数进行一些修整来解决,例如“转发构造函数(forwarding constructor)”、“有保障的编译期构造函数(guaranteed compile-time constructors)”以及“继承的构造函数(inherited constructors)”等。

第三件事是“剔除语言里的一些令人不愉快的瑕疵”,也就是说,修整一些细小的不合常规或不方便的东西,它们对有经验的C++老手不会产生什么影响,然而却可能严重打击C++新手。一个非常简单的例子就是:

vector<vector<double>> v;

在C++98中,这里有一个语法错误,因为“>>”被看成一个单独的词汇标记,而不是两个“>”。正确的写法如下:

vector< vector<double> > v;

对于C++98这样一个“不近人情”的规则,虽然有足够技术上的理由,但这不应该强加给任何背景的新手(包括其它语言的专家)。如果编译器不接受前一种最为明显的v的声明形式的话,那么C++用户和教师都会在这上面浪费大量的时间。我希望这个“>>问题”以及其它一些瑕疵都会在C++0x中消失。事实上,在和Francis Glassborow以及其他一些人的工作中,我一直努力去系统地消除出现频率最高的此类语言瑕疵。

荣耀: 和大多数人一样,我认为C++缺乏一个大一统的库是阻碍C++更为广泛地使用的关键原因,您认为现在C++社群有足够的资源来开发一个像Java或.NET那般规模的库了吗?如果没有,我们该怎么做?我发现使用形形色色的第三方库非常不方便(一个插曲。我在使用微软Visual C++时,有时希望使用STL组件,例如vector,但由于我大幅使用了MFC,而MFC中也有类似的容器,所以,虽然vector更好用,但为了避免因链接两个不同的库而导致文件体积增大,我最终往往放弃使用标准库。不过,倘若标准库提供了MFC所提供的所有功能,我将肯定全部改用标准库)。

Bjarne: 毫无疑问,MFC是迄今为止被广泛运用的最糟糕的基础库。它违反了一个好的C++设计应该遵循的大多数原则。它严重地扭曲了许多程序员对于“什么是C++”的看法!

当然,我也认为缺乏全面且标准的基础库是C++社群的一个主要问题。对于个体程序员来说,这也许是他们面对的最困难的问题。我在《C++语言的设计与演化》中谈到了这一点,至今我仍然坚持这一点。

C++社群没有一个有钱的公司来支持“平台中立的”标准库的开发 — 从来没有,跟其它专有语言(以及它们的“标准”库)相比,这一直是阻碍C++发展的一道藩篱。和专有的基础库的扩展速度相比,我们对标准库的扩展是很慢的(不过和其他ISO标准库相比,我们的扩展速度应该算是比较快的)。我们还期望能够和新兴的非标准库(译注:如Boost)逐渐达成更为平滑的整合。别忘了,当年MFC以及其它“后80年代”风格的库被设计出来投放市场之际,尚无任何标准库可作它们的构建基础。因此,我希望今后的专有库和开源库都能充分尊重标准库,以便使它们之间的互操作变得容易 — 至少容易一点点。

C++社群并不是为大规模设计和实现而组织的。C++社群中也没有什么传统或惯例。和其它社群相比,我们缺少一个“统帅”,他可以有效地激励新库的创建,否决或“保佑”我们的努力和成果。然而,我对“统帅模式”是否可行心里没底 — 除非你所做的事情只是基本的模仿而已。Boost(www.boost.org)是一个优秀的成果,它的某些方面已经超出了模仿的范畴,然而它仍然缺乏一个明确的目标以及用于维持自身发展的权威机构或权威人物。

在以下三个相关的领域中,大规模的合作努力对于C++社群而言是必需且有意义的:基本的并发编程库(包括线程、锁和lock-free算法等);平台无关的操作系统服务库(目录和文件操纵以及套接字等);以及GUI编程库。前两个看起来是可行的,但要想建立一个标准的GUI库,也许从技术上、经济上尤其是政治上都显得太困难了。

荣耀: 是的,我相信对于许多C++程序员而言,一个标准的GUI库是极其重要的。许多人以为GUI库不是一个太复杂的问题,但我却认为这可能是最棘手的问题。为了解决它,我们可能需要难以置信的大量的资源。同时我认为政治问题可能是其他问题的根源。举个例子,我们知道Windows的GUI风格和Java的GUI风格是不一样的,甚至不同版本的Windows的GUI风格也不一样。而且我们知道GUI风格很不稳定,它发展演化得非常快,而且很容易被微软这样的大公司所引导和操纵。

Bjarne: 在这个问题上,资源(包括财力和人力)是一个关键问题,与“保持设计蓝图的一致性”同样重要。另外,即使我们已经有了这三样东西:蓝图、人力(数打甚至更多)、以及财力(至少要担负得起一些优秀的人在这个项目上的全职工作),这仍然会是个需要N年才能完成的项目。而SUN、微软、苹果以及其他主要竞争对手将会做些什么呢?毕竟包含了一个工业强度的GUI库的ISO标准C++对于它们的专有系统可不啻一记重击啊!我的猜测是它们大多会通过强大的市场手段来保护既得利益,标准委员会可不是为了在这种市场环境中呼风唤雨而组织起来的,我们至少还需要一个工业社团的支持以及和开源社群主力军的联盟。

荣耀: 在您看来,C++要想继续向前发展,除了开发一个更为广泛、更具威力的库以外,我们还应该做些什么?

Bjarne: 我认为库方面的工作是关键。标准委员会没有足够的资源去创建一个全新的库,而且无论如何“让委员会设计”都不是个好主意。如果人们创建了新的库,在紧密相关的领域把它们的努力融合起来以达到冲突最小,并且文献记录良好(并非只记录细节,还有设计原则),那么他们也许可以把这些东西带到委员会来,并有希望让它们成为标准。如果没有成为标准的话,至少他们的努力有最大的机会成为“准”标准。有此打算的人应该把C++真正当作“C++”(使用继承、模板以及异常等)来看待,而不是从其它特性不是那么丰富的语言中抄袭设计而来。如果一个库的设计被发现没有用C++最佳地表达出来,那么它被接受为标准的几率微乎其微。

另一个可作贡献的领域是对技术的开发和推广。C++往往被以“很不理想”的方式使用着,MFC就是一个典型的例子,它甚至还达不到80年代中期对一个良好的OO设计的看法!我所说的“不理想”,是指没有达到它所能达到的可维护性的设计(通常这是由于对设计决策的糟糕的分解、低劣的封装以及对概念的拙劣表达而造成的),而并非指在外部压力下要尽快把项目赶出来的个体程序员或团队的“不理想”。通常,一个较好的设计和现有的实践是背道而驰的,后者往往需要立即付出代价(往往在很短时间内)。显然,任何显著的改进都需要在“一切照常”的基础上,并且至少追加学习所需的代价以及时间上的延迟。

这就把我们带到了最重要并且也许是最明显的改进实践的途径面前:教育。我们对编程和设计的教育必须比当前做得更好。新的语言特性、新技术以及新库碰到了不能理解并使用它们的人仍然是无用之刃。遗憾的是,我们恰恰缺乏优秀的C++入门书籍。Francis Glassborow的新书《You Can Do It》(译注:中文版《C++编程你也行》即将由人民邮电出版社出版)和Koenig & Moo的《Accelerated C++》是打破旧式而令人厌烦的教育方式的例子。那种教育方式把C++当作一个“稍微好一点的C”或者一门在实现“真正的面向对象”方面失败的语言。前者倾向于用一大堆语言技术细节来迷糊和恼怒读者,偏离了学习好的编程及设计技术的正确道路。后者通常除了存在这些问题之外,还没能够教会在性能攸关的领域里编程的必要技术,而且没有让人感受到静态类型系统的价值。尤其遗憾的是,Glassborow和Koenig & Moo的书的风格都不是编程入门教材的传统风格,这也阻碍了它们的广泛普及。

我自己的两本书《C++程序设计语言》和《C++语言的设计与演化》的目标读者则是已经知道如何去编程然而不知道如何使用C++的人。这两本书确实满足了这部分读者,但我们要做的还不止这些。

任何时候只要有可能,程序员都可以通过将程序的主要部分用ISO C++来写,并将系统依赖性封装起来,从而来支持标准。也就是说,把系统依赖性限定到特定的区域,并通过以标准C++表达的接口去访问它们。通常这并不容易做到,因为厂商总是怂恿程序员在代码中使用专有的、排他性的特性,但这就影响了可移植性,从而使程序移植到其他系统上非常困难。然而,站在应用构建者的立场来说,从长远来看,挣脱厂商的束缚、维持可移植性是一件好事,这种隔离系统依赖性的努力终将从经济和技术两方面都得到回报。

荣耀
2005年1月
南京师范大学
www.royaloo.com


评论列表
铅笔谬书
re: C++热点问题一席谈(上) Bjarne Stroustrup 2005新春专访(转载)
怎么看,怎么爽,好日子要来了

发表评论
切换编辑模式