< 更新 更早 >

编程的未来

2012年9月按:年少轻狂的时候,总爱指点江山。文中有些真知灼见,却也有一些局限于当时的眼光的谬误。比如,今天的Java早已不是被阉割的C++,而成为一门充分融合动态语言特性的静态语言,其JVM也成为形形色色的动态语言的筑巢之处;再比如我那时对ruby元编程所知甚少,竟然推崇eval,真是贻笑大方。不过为了保持历史原貌,就不对博文做删改了。博文后面还附有当时和fuliang进行的一场讨论。今时今日我的工作中大量地使用Java,有些对Java的看法已经失效了(在我的团队涉及的编程领域,Java的类库的易用性要好于同功能的C++类库,培养一个能避开Java语言陷阱的Java程序员要比培养一个避开C++语言陷阱的C++程序员成本低一些),但对C++基本的喜爱的理由,依然是当初的那些,依然反感懒惰、浮躁的程序员。

2008年10月07日按:Java的反射机制(尤其是动态代理)使这门语言变得准动态,为这门语言增添了一抹光辉。JVM一直在变得更快,垃圾收集算法一直在优化。

看完《J2EE Development without EJB》,我开始深切地怀疑,是否我应该收起对Java的恨,开始认真地学习它内里的更多技术?

此文中一个相当过时的地方,是关于类库。它的概念应该更加扩展,包含框架和中间件。

有一句话,我觉得对程序员是至理名言:编程未来的趋势是库,动态的脚本语言和虚拟机。这句话我一听就觉得很对,可是对它的领会(尤其是后半句话)却花了很久,现在也还在半路上。

我一直很喜欢C++的一个基本的设计思想,就是有些事情是语言该做的,更多的事情却应该交给库去实现。正是这种思想,使得C++可以应用于各种领域。语言应该给程序员足够的自由,从最底到最高层,从最机器的层面到最贴近现实的层面,然后,用这种语言,我们再来写库,用于各专有的领域。C++成为了工业标准,它写成的库可以被各种语言使用、绑定,它被用来写各种语言。任何宣传C++的没落的言论都是窄视的,它没有看到C++是今天的许多事情的一个基石。C++ 是静态语言所达到的一种极致。

C++与C的兼容以及对其规范化的推动,现在看来是做得再聪明不过的一件事情。时至今日,许多库依然用C 写成。C的生命力是C++成功的最重要的基石。在领略C++的无数美妙之处的过程中,我曾经以为C已经没落了,后来却在实践和更多的对源码(gcc和 Linux内核)的阅读中重新意识到C的永不消逝的价值。看到C里的OOP甚至 AOP的种种实现,正如看到SICP中用Scheme做的编译器时一样,我意识到,更轻小更简单的语言,却能允许人类智力的最完全的发挥,也在一定程度上明白了为什么Linus会说“C++ to C is lang cancer to lang”。很多C++里的东西,现在看来都很语法糖衣。然而,它最可贵的价值之一就在于,用一种语言,使我们学会了OO、多态和泛型的思维,同时也提供了一种对这些思维的描述。C++是设计模式最好的描述语言,我看到许多书用Java这种先天不足的语言来描述设计模式,就觉得别扭。

是的,我恨Java,这种感情与日俱增。虽然我一直都在关注和学习它的技术,包括EJB,Struts,Hibernate和Spring,并且《J2EE Development without EJB》一书使我对这门语言背后的技术有了更大的好感,再加上最近看到一篇文章尖锐地指出Java并不慢,但是,刨去了所有没有根据的偏见,使我憎恨这门语言的核心理由却无法被撼动。不记得谁说过,Java是他第一次学习一门新语言没有感到任何新的惊喜,没有发现新的思想。我还要加上后半句:“反而感觉到一下子丢了好多东西”。Java使OO的一切都变得不再美妙,使一切变得麻烦,这就是我们为所谓的简化付出的代价。C++庞大而精深,任何一个怕难的人都可以栖居于这个语言的简单部分,而让写库的人去充分使用这个语言的其他层面。有了编写良好的智能指针库和对其恰当、适度的使用,所谓的用一种语言来 “把程序员从内存管理的泥潭中解放出来”就相当可笑。程序员的不自律和不善用库没有任何借口。程序员之所以爱好编程,就是喜欢那种一切都可以做到,一切都可以掌控的感觉。静态语言不能丢失指针,不能丢失引用,程序员不应该对const &厌烦。为什么除了栈我们还需要堆?在你离开作用域的时候,栈会严格地帮你回收利用你用过的东西,但你却带不走自己用过的东西。有了堆,你可以带走了,你可以在应该回收的时候再放手。托管却使你完全失去了对一切的控制力,你不得不把东西丢得满地都是,等待机器人来捡你的餐巾。而且Java写的程序就使用的时候感觉,也常有内存释放的问题,也会崩溃。那么,当初何必呢?静态语言不应当放弃自己对内存的管理,自己束缚自己手脚。

Java 的流行的关键是,它对类库的相对统一的捆绑,它的用虚拟机跨平台,它的集成了多线程,它在Web方面和嵌入式方面采用的先进的技术和框架。这些构成了它的生命力,有人说它会消亡,那是不可能的。我不是看不到它的生命力,而是我一直宁可这些概念和技术是基于一门不那么丑陋的语言,尤其不能容忍喝 Java咖啡的人不知天高地厚地耻笑C++,而且对那些把Java称为初恋的程序员感到极度恶心。我无法相信,发明Java的人会是真正资深的C++程序员,他们应该是在早年C++艰苦创业时受了苦的程序员。顺便说一下,我相当明白为什么大家会把这些技术堆到Java身上,Java语言自身的简易(从编译实现的角度)和内存托管功不可没。一切都是时机和真实的历史进程惹的祸。

在看了D&E之后,我对Stroustrup相当佩服,我佩服他在各种程序员的口味面前坚持住了自己设计语言的哲学,保持了语言的纯洁,使得C++的扩充并没有使自己走形,这不是任何人能做到的。举两个我最欣赏的例子:纯虚函数=0的语法,和后缀++重载的方式(operator++(int))。不过也可能有人对这个最不以为然。讲讲我最喜欢C++的一些地方吧:尽量保持少的关键字,善用运算符,有运算符重载(可惜还不够充分自由,要是有自定义操作符的功能就好了),有同名函数重载,有枚举,精心设计的构造和析构体系(就像栈一样让人放心),类的定义和实现分离,模板以及相应的泛型编程,有作用域的typedef以及traits技术,保留了强大的C预处理宏(学C++先要学会不用宏,然后学会善用宏,可惜define间较难相互利用),有指针和引用,有各种const,有名称空间,进行强类型检查却又保有隐式类型转换,有完全可以善用的多重继承,有精心设计的虚函数多态体系以及可爱的->,很多东西交给了库而不是捆绑入语言,这使得我们可以有较多选择。这里太多太多东西Java没有了,而以上每一样技术都是程序员的好朋友。C++自己的问题在于编译器的实现还不够完美。

应该说,没有使用过多个库/类库(尤其是开源库)的C++程序员不算C++程序员。各种通用用途或专有用途的库对于程序员的成长是非常有帮助的,它会培养程序员搭建编程环境,靠文档和例子迅速上手,用Hack解决各种Bug、移植性或依赖性问题,对库进行定制的能力,这些可是保持程序员在不断涌现的新技术面前屹立不倒的能力啊。由于库通常有各种语言的绑定,库也使程序员能迅速摆脱语言的束缚,只要有库,程序员的能力就不受限制。剩下的就是人月神话的问题了。我一直都在涉猎各种类库,他们的源代码就如同经卷,如果有足够的时间,好想像玄奘一样坐下来汲取它们的精华。

在C++之前,(从小学起)我用过 Logo,GWBasic,C, VB。在C++之后,很少再有什么语言能给我真正的惊喜,直到我大范围接触动态脚本语言。Bash是最初的关于脚本的惊喜,但现在看来,它太不脚本了,不够动态。后来用过或学习过,汇编(x86,一点PowerPC,现在正在学MMIX), Make, LaTeX, Matlab, ActionScript, JavaScript, Lex/Yacc, CSS/XPath, XML/JSON/YAML, Grapghiz, Metapost,Scheme,Squiril,PHP,Python,Ruby,Lua,Erlang 等语言,应该说各色语法都见过了,都有一段使用的时间,有些比较钟爱的语言还是在多个时期分别使用过,有反复的咀嚼和重新发现。

最近一段时间做了个网站(DHTML/CSS+LAMP+Ajax),加上对Prototype源代码的阅读,使得我对JavaScript有了重新的发现,同期的Ruby学习也令我惊喜不断,我向所有人推荐这两门语言,这两门动态语言,最适合打破C++给程序员的静态思维,PHP也不错,除了用它做网站后端,用来做零活也不错。相比之下,我还是不那么喜欢Python,有待将来重新发现它了。JavaScript对于函数的内部变量的处理方式(挂为函数的一个属性)和把函数当做普通变量的做法,使得它的函数既能当Lamda函数用,又能当(一个可以不断扩展的)类用,甚至可以当包用,相当伟大的设计。 JavaScript甚至发展出JSON作为数据共享格式,比XML好多了,我相信它将在分布式应用中发挥相当重要的角色。JavaScript和 Java根本是两种完全不同的语言,完全不同的两种思维范式,我从前对它有太多误解。由于JavaScript和C很像,所以掌握其使用不难,但Ruby 则将要求你习惯另外一种思考问题和书写语句的方式。Ruby中的迭代器是它最大的亮点之一,它对前一语句的返回值与{}块进行了相当具有启发性的使用。另外一个特别需要适应的思维是,这里没有强类型检查,只有duck-typing,即如果我们只需要一个对象像鸭子一样叫,只要这个对象能够像鸭子一样叫,我们就接受它是鸭子,不管它事实上是什么。我原先对动态语言的无类型一直不适应,这次使用时才充分意识到它的威力。Ruby很有更多美妙的地方,这里就不提了。

若干年前,有书推荐说,学了C/C++后,Java是相当合适的第二门语言。现在才知道大错特错。程序员的第二门语言应该是一门脚本语言,用来快速完成一些零活,也用于使程序员的思维灵活化。脚本语言的设计思路绝不能像静态语言一样,让语言更纯粹,事情让库做。脚本语言要内嵌许多常用的功能,不仅是通过核心库来提供这些功能,更是要在语言层面支持更方便的调用。正则表达式就是其中最起码的一个,数据库的访问、多线程也是相当重要的(且看Ruby的mutex.synchronize{})。

动态语言还有一个妙处在于eval(),我们可以动态地用脚本生成脚本的字符串,然后执行,这也是Bash当初令我惊喜的原因之一。自然,可以像访问一个关联数组一样访问创建过的变量、函数、类和Symbol,更是动态语言给程序员的最大自由。这两个功能,前者使我们能使用使用语言构筑的语言,后者使我们能够进入语言的元层面。前者建起来,后者挖下去。这样我们得以超出语言提供给我们的单一平面,有了第三个维度。

在文章的最后,简单地说说虚拟机吧。

从使用VMWare在各平台上调试程序,到使用各种模拟器玩游戏,再到使用模拟器进行嵌入式开发,虚拟机的重要性不言而喻。始终希望Fusion或者seamless的技术能够发展得更成熟——有了这种技术,我们可以在一个操作系统上无缝地使用另外一个操作系统上的程序。我可以装个真正的双系统,以其中一个操作系统为主操作系统,需要时不用重启可以调用另外一个操作系统中的程序,最关键的,是这个程序的运行环境,是真正基于那另外一个操作系统的,这样不会有Wine的尴尬,不会有VM的慢。在多核的情况下,可以有多个操作系统来分别使用一个核。这样的技术是可以实现的!只是还没有。如果有足够的内存,这样的技术就可以足速运行。现在的情况是,软件的开发跟不上硬件的发展,即使具备了相应的计算能力,却不能享受到这种便利。一种可能的设想是,不要把虚拟机建在应用层或者内核层之上,而是在内核下面,在BIOS和Booter之间,并像VMWare Tools一样,在应用层也有呼应,两相配合。Intel和AMD已经在CPU里对虚拟机做了支持,然而就使用它的KVM的性能表现来看,我还是比较失望的,可能是我没有恰当使用,或者KVM还在刚起步的发展阶段吧。还有Knuth的结合了各种计算机架构优势的MMIX芯片,目前还只有虚拟机,我希望这样的虚拟机能够硬件化,然而这样的事情,却要等待真实的历史进程了。

然而其实那句话所说的虚拟机,应该不是指这种虚拟机,而是指语言实现用的虚拟机。Chrome所用的JavaScript引擎V8,以及Mozilla的SpiderMonkey引擎,它们也是一种虚拟机,然而是属于动态语言的虚拟机,解释语言的虚拟机。这又涉及到自动状态机……对这些方面了解不多,就此打住吧。

评论

fuliang 2009-05-01

我感觉Java的面向对象模型要比C++要好,C++是一个追求效率而不惜牺牲其他空间和优美方面的语言。

一、C++没有interface的概念,这是面向对象概念的很大的一个缺失,当然纯抽象类就是interface,但至少是概念上的一种缺失。因为interface和抽象类在面向对象中是完全不同
的概念,设计的目的也不同,抽象类是为了别人继承,而接口一种契约和规范。

二、C++的继承体系不够完善。C++在每个对象中安插一个指向虚函数表的指针来完成多态,实则是以空间换时间的做法。而Java有着完善的继承体系的实现,查找方法通过super指针遍历类体系结构,这是大多数语言采用的方法。C++这种做法导致类继承函数隐藏的诡异行为(子类一个不同方法签名相同方法名的方法会隐藏掉继承下来父类的方法),实在不爽。

另一方面C++缺少单根继承(Java的所以类的根都是Object),这其实也是面向对象概念的一个缺失,一切都是对象。这样一些类就没有了一个统一的约束。

三、C++引用这个东西的名字几乎起错了,实际应该叫别名,而不是到处都在说引用就是别名这个东东。这点与大多数语言都不同。

四、C++显示对象存在(Java中的对象都不是显式的,是由引用持有的,你看到的都是引用)只会导致误用,由此导致对象的无味拷贝。从而引出像拷贝构造函数,以及引用传递的
复杂概念来支持和避免带来的问题。

五、C++拷贝构造函数,对指针成员深拷贝无能为力,需要手写虚clone方法实现prototype模式,并且没有同一个一个约束,这样是缺少单根继承所造成的后果。而这一切Java都是内置的。

六、C++的异常很不成体系,使用起来让人觉的很是不爽。

七、C++缺少反射机制,这点对使他真正的成为“静态语言”,许多更优雅的框架设计从此离他远去。
其他方面:

八、C++的多维数组作为传递一直很无奈,而Java则足够智能。

九、C++的返回函数内的对象一直没有很好的解决方法。局部对象直接返回造成无意义的拷贝,返回地址则因失去作用范围而无效,使用new在堆中申请需要让别人去delete,而这使用者可能一无所知。使用智能指针,让人阅读代码痛苦不堪,我曾读过Firefox源代码全是智能指针,导致Eclipse ctrl快捷键阅读代码完全失效。这就是托管语言,垃圾回收爽的地方,从而带来代码上的优雅。

九、C++的string和char *不能自动相互转化这点非常不爽。

十、C++的stl库设计缺少和向数组这样基本结构转换的接口。以及一些容器设计不够优雅,
如map的count,find这个函数名字叫的莫名其妙。

十一、其实大量的程序员不会使用C++的const,由此造成传递参数的时候,如果没有参数没有const修饰,而无法传递常量的困苦。以及其他赋值方面没有一个约束机制,例如char *s = "abcd";很自然的一个语法却潜藏阴险的问题。

当然Java也有一些ugly的语法,例如不支持默认参数,泛型是一种擦拭机制。但基本上Java是在去除C++语法的复杂性和糟粕的东西,而吸取了其精华的部分,让你远离了危险的代码而保持健壮性。

C++是设计模式最好的描述语言,我看到许多书用Java这种先天不足的语言来描述设计模式,就觉得别扭

我倒和你有相反的想法,C++描述设计模式要考虑的C++语言的语法的太多,而Java则简单明了的表明了意图。

Java使OO的一切都变得不再美妙,使一切变得麻烦,这就是我们为所谓的简化付出的代价。

我不知道有哪些不美妙的地方。至少我感觉Java的OO机制要比C++美妙的多,C++在向下兼容c的面向过程和一切以效率为目标,以及在stl试图得到使用函数式语言的美妙感觉而背弃面向对象的方法,让它变得不伦不类。

静态语言不能丢失指针,不能丢失引用

Java的引用就是安全的指针,在面向对象语言中,指针的++,--操作不仅危险而且显得很别扭,C++引用其实是一种在提供一种防止大对象拷贝的方法,而Java完全不会有大对象拷贝的情况,而引用的副作用可怕的很。在引用传递的时候,形参的突然改成别的对象的别名,
实参的引用仍然很无辜的以为还在是原来那个对象的别名,虽然可以加上const T &t来限制这个东西。C++给程序员的责任太多,我每次写C++代码的时候都要小心翼翼,不是在考虑设计而是在考虑细微的语法所带来的别人如何使用问题。

utensil 2009-05-05

谢谢你的来访。原文固然带有个人的强烈偏好因素,然而你的观点我实在难以苟同。

首先想复述一些我原文中的论述,因为这些是我的主要观点:

C++庞大而精深,任何一个怕难的人都可以栖居于这个语言的简单部分,而让写库的人去充分使用这个语言的其他层面。...

程序员的不自律和不善用库没有任何借口。...

程序员不应该对const &厌烦。...

托管使你完全失去了对一切的控制力,你不得不把东西丢得满地都是,等待机器人来捡你的餐巾。而且Java写的程序就使用的时候感觉,也常有内存释放的问题,也会崩溃。

文中你用了分散在多处的、非常多的篇幅来反感const &和C++中的引用,这是你之前某篇博客的观点的重复和展开:

九、 C++的返回函数内的对象一直没有很好的解决方法。局部对象直接返回造成无意义的拷贝,返回地址则因失去作用范围而无效,使用new在堆中申请需要让别人去delete,而这使用者可能一无所知。使用智能指针,让人阅读代码痛苦不堪,

十一、其实大量的程序员不会使用C++的const,由此造成传递参数的时候,如果没有参数没有const修饰,而无法传递常量的困苦。以及其他赋值方面没有一个约束机制,例如char *s = "abcd";很自然的一个语法却潜藏阴险的问题。

Java的引用就是安全的指针,在面向对象语言中,指针的++,--操作不仅危险而且显得很别扭,C++引用其实是一种在提供一种防止大对象拷贝的方法,而Java完全不会有大对象拷贝的情况,而引用的副作用可怕的很。在引用传递的时候,形参的突然改成别的对象的别名,

实参的引用仍然很无辜的以为还在是原来那个对象的别名,虽然可以加上const T &t来限制这个东西。C++给程序员的责任太多,我每次写C++代码的时候都要小心翼翼,不是在考虑设计而是在考虑细微的语法所带来的别人如何使用问题。

三、C++引用这个东西的名字几乎起错了,实际应该叫别名,而不是到处都在说引用就是别名这个东东。引用这个词与大多数语言语义都不同。

四、 C++显式对象存在(Java中的对象都不是显式的,是由引用持有的,你看到的都是引用)只会导致误用,由此导致对象的无味拷贝。从而引出像拷贝构造函数,以及引用传递的复杂概念来支持和避免带来的问题。这当然是C++不得不做的,因为class的概念根深与struct,指针的作用指出也必须是这些显式存在才会发挥作用。

正是因为你从一开始错误地将C++中的引用理解为别名,所以才使得你在使用中在概念上排斥const &的做法,认为这是语言强加的语法细节。然而,这恰恰不是纯粹形式的、繁文缛节的语法,而是因为C++所保有的细微的控制的粒度而对语法产生的需要。简单地说,你不需要复杂的语言去描述简单的世界。遗憾的是,软件的世界是复杂的,底层是复杂的,对底层敬而远之的人依然要面对底层的复杂,只是遭遇的时候,他们更加不知其所以然,脸上的表情更茫然罢了。C++的许多语法的复杂,都可作此解。

而Java中对这些问题的隐藏,在不自律的程序员手中,一样问题重重,我使用Java写的软件,崩溃得比C++写的程序还要多(都不是我写的,而是广为使用的软件),一看,常常是空引用没有处理好。为什么?因为Java程序员,往往是C++的逃兵,是自律的逃兵,懒惰而推脱责任。JVM的底层,都是一流的程序员用C在认真严谨、小心翼翼地写着程序。而Java Guys,往往依赖着IDE,生成并修改着具有臭味的代码。

一、C++没有interface的概念,...当然纯抽象类就是interface,但至少是概念上的一种缺失。...

C++是一种多范式的编程语言。他自身提供了很多基础设施来让不同的“概念”来施展。如果有了纯抽象类,有了可以当接口使的基础设施,你仍然要强辩说语言缺了这个“概念”,那我无话可说。概念缺失的说法,实在是一种教科书为了解释interface的存在而培养起来的你们java guy的莫名其妙的优越感。令人难以苟同。

五、C++拷贝构造函数,对指针成员深拷贝无能为力...这样是缺少单根继承所造成的后果。而这一切Java都是内置的。
另一方面C++缺少单根继承(Java的所以类的根都是Object),这其实也是面向对象概念的一个缺失,一切都是对象。这样一些类就没有了一个统一的约束。

六、九、十、

C++是一种类库百花齐放的语言。Java当中的类库世界是单极的,语言自身提供了过多的东西,使得太多的功能需要依赖语言自身,一旦这些基本功能有Bug,无从修复也很难绕开。

各类库往往有自己的方法来解决一些问题,而这些问题,由于是软的,所以可以自行解决,而Java中庞大的统一着Java世界的标准类库,使用其时,就依赖着其底层的实现代码,而这些代码,常常不是Java写的。逃避底层再一次显现出其依赖底层的悖论。这是对开源精神的一种违背。

你所津津乐道的单根继承,在wxWidgets库中实现着,在许多类库中也实现着。这个应该交由库来实现的事情,真的应该要让语言来做?只能有语言这一种方式来做?语言做的方式,真的适合所有的场合?

C++中任何的库(包括STL)都退化为一种实现的建议,你得以采选,得以拥有调优的自由。Java中,这一切你想自己做都是不可以的。语言已经自以为是的做好了,并得意洋洋地说:菜鸟们,用就是了!单一的选择,懒惰的抉择。帝制。从clone到异常体系。

这也正是各大厂商选择Java作为它们中间件的语言的原因之一,因为这样单一的语言,最适合针对质量参差不齐的程序员而言的软件工程,代劳大量重复工作的IDE的出炉也有了理由。钱有得赚,因为语言本身不争气。

你所论述的深层拷贝问题、多维数组、string、STL等问题,只让我感觉到你对C++的某些基础设施还不够熟悉。自己不会用和语言不能做是两回事。具体此回复暂不展开。

七、C++缺少反射机制,这点对使他真正的成为“静态语言”,许多更优雅的框架设计从此离他远去。

对Ruby和其他动态语言了解的越多,我就越感觉到,Java是一种尴尬。你觉得Java API式的反射机制与Ruby更为native的反射机制相比如何?Java真正需要动态的时候,只好靠Groovy、Scala出场。Spring将反射机制运用的出神入化,仍然使我们面对繁琐的XML配置。

在反射对C++的程序真正有用的时候,类库却也可以有自己的实现,wxWidgets自身的RTTI体系就是一例。反射的多种用途在C++中其实可以退化为多种表达形式,而且更贴合其使用情境。反射的滥用,却往往违背OO,违背了解耦,增添了依赖性。

二、 C++的继承体系不够完善。...
子类一个不同方法签名相同方法名的方法会隐藏掉继承下来父类的方法...

此现象闻所未闻,到时写个程序验证一下。

我不知道有哪些不美妙的地方。至少我感觉Java的OO机制要比C++美妙的多,C++在向下兼容c的面向过程和一切以效率为目标,以及在stl试图得到使用函数式语言的美妙感觉而背弃面向对象的方法,让它变得不伦不类。

从main的病态放置到仅有单继承(也无任何类似Ruby的Mix-in,只有一个可怜巴巴的起一点规约作用和功能描述作用的接口),从析构函数的消失到界面与实现的强制混合,我没有看到Java中任何OO之美,只见到病态的束腰。Java对OO的理解完全是一种偏差,一种强行的简化。

“stl试图得到使用函数式语言的美妙感觉而背弃面向对象的方法”,OO的本质其实还是强化内聚和解耦,是分清责任与协作,STL恰恰是这种思想的杰作之一。我不明白你所说的背离是什么意思。

C++并不是OO的始祖,也不是OO最好的表达,C++也远不是一门完美的语言。然而,从语言层面(非平台层面),Java没有资格说C++什么,它并没有取C++精华,而是C++的一个拙劣的简化和改造。

语言之争往往无益,因为各自熟悉顺手。回复中语气冒犯之处,还请海涵;偏颇之处,也欢迎指出。

fuliang 2009-05-05

程序员的不自律和不善用库没有任何借口。...

如果一个语言只有教父级别的人才能应用自如的话,那么这个语言是失败的,不能怪程序员功力不够,不自律,应该考虑这个语言本身的陷阱和琐碎的规则了。

正是因为你从一开始错误地将C++中的引用理解为别名

既然我c++的引用不是别名,请教一下应该做何解释。

单根继承,在wxWidgets库中实现着

库的支持只能说是一种对语言修补,语言级别的支持往往比第三方库支持来的更自然。

C++中任何的库(包括STL)都退化为一种实现的建议,你得以采选,得以拥有调优的自由,Java中,这一切你想自己做都是不可以的

Java事实提供了更多可以选择和定制的东西,Java语言的接口的概念本身就是一种建议和契约,STL中的Set(Map)只有一种SortedSet (SortedMap)而且从名字根本无法晓得这个Set里的元素排好序的,这种潜规则需要仔细阅读文档才能知晓。而Java Set接口之下会有SortedSet,TreeSet,HashSet可供选自,当然可以自己实现Set接口来定制自己需要的Set.

因为C++所保有的细微的控制的粒度而对语法产生的需要

这点我也很赞同,这种需要是通常一个概念很容易被误用,然后又出来一个东东来束缚一下。

对Ruby和其他动态语言了解的越多,我就越感觉到,Java是一种尴尬。

如果那动态语言的强项和静态语言的弱项相比这种尴尬是任何语言都不可以避免的。

你所论述的深层拷贝问题、多维数组、string、STL等问题,只让我感觉到你对C++的某些基础设施还不够熟悉

某些基础设施希望你能指出,如果是第三方的库的支持能做的话,与标准库的支持相比,我感觉着仍然是一个缺陷。

从析构函数的消失到界面与实现的强制混合,我没有看到Java中任何OO之美,只见到病态的束腰。

有垃圾回收析构函数固然是一种多余,并且关键的资源用户可定义close或者dispose方法来释放资源。界面与实现的强制混合?我不知道你在说哪方面,是C++头文件和.cpp的分离么?事实上interface本身就提供了这个功能,并且可以有多个实现。

Java中,这一切你想自己做都是不可以的。语言已经自以为是的做好了,并得意洋洋地说:菜鸟们,用就是了!单一的选择,懒惰的抉择。帝制。从clone到异常体系。

如果有了一个很好的基础设施的话,都被认为是一种懒惰的抉择,帝制的话,那最好用汇编吧!单一的选择?为什么单一,你可以自己做想要的实现,并且直接可以融入体系之中。

utensil 2009-05-05

发现昨晚的回复写着写着走调了。

...如果一个语言只有教父级别的人才能应用自如的话,那么这个语言是失败的

C++真的需要教父级别的人才能良好运用吗?世上依然有许许多多愿意自律、愿意谨慎、愿意沉思的人。

因为C++所保有的细微的控制的粒度而对语法产生的需要

这点我也很赞同,这种需要是通常一个概念很容易被误用,然后又出来一个东东来束缚一下。

你赞同的不是我的观点。这样可能被误用的形式是为了控制更底层的东西所设置的。二流程序员自己不断误用,却不断怪在语言上。真是令人难以气平。我没有感觉到C++给我的束缚,一切如此顺理成章,而Java却给我带来了很多混乱。而且Java是一种很罗嗦的语言,一吨一吨的关键字,远不如C++的几个符号简单明了。

库的支持只能说是一种对语言修补,语言级别的支持往往比第三方库支持来的更自然
Java事实提供了更多可以选择和定制的东西

昨晚我的论述有些走调了。因为我其实是想论述有些基础设施由语言来提供这一点,是有利有弊的,而不如你可能认为的,利远大于弊。如果C++中在语言层面提供了这些东西,就会妨碍了自身的灵活,限制了自身所能运用于的领域,C++也不会取得今天这种基石的地位——你可能论述说,基石是C而非C++。然而C++提供了从底层到抽象的各种编程范式来供项目的不同部分栖居。

set...有序...

你可能不了解的是:Specification没有规定其STL中的set是否有序,这是由实现自行决定的,只规定了set的时间复杂度。使用C++的set的时候,也并不需要知道其实现细节,而且其使用也不应依赖于实现细节,而是如使用黑盒子一般使用其接口。

这种潜规则需要仔细阅读文档才能知晓。

什么时候起,我们不应该鼓励RTFM和RTFS的精神,而是变成了要鼓励Read the Fantastic Name的精神了?中国的许多程序员往往一知半解地抄用着别人的代码拼凑起来,遇到问题连google都不舍得,更不肯阅读文档。这种浮躁,也适宜用来指责一门语言?

既然我c++的引用不是别名,请教一下应该做何解释。

某些基础设施希望你能指出,如果是第三方的库的支持能做的话,与标准库的支持相比,我感觉着仍然是一个缺陷。

如果无意潜心C++,又何必解释这些概念和运用的细节。只是若博主要拿这些自己尚未掌握的知识来论述C++的不足,却有些贻笑大方。如果一个学C++的人只了解了语法、STL,再加上MFC,那根本还没开始涉足C++的美妙世界呢。

因为博主也未具体说明你是怎么感觉到不智能、不能转换什么的,我的解释也没有针对性啊~若要论具体的话,代码说话。可以你用Java实现某某事,我再用C++实现等等。但你我都没这么好精神在语言之争上浪费光阴吧?

C++中,引用的最大用途是传引用或返引用,也就是参数或返回值中的&。这远比传入指针安全。你见过谁把C++中的引用当别名使的?一个{}之内,谁喜欢给一个变量起两个名字?只有教材,那误导人的教材,才会把应用理解为别名。

二、 C++的继承体系不够完善。...子类一个不同方法签名相同方法名的方法会隐藏掉继承下来父类的方法...
此现象闻所未闻,到时写个程序验证一下。

我写了个简单的测试程序,没有发现此问题。请博主再验证一下。

有垃圾回收析构函数固然是一种多余,并且关键的资源用户可定义close或者dispose方法来释放资源。

我知道。而且你回避了我说了几次的“Java中对这些问题的隐藏,在不自律的程序员手中,一样问题重重”。

我举了一些未必恰当的例子来说明在我眼中Java对OO的扭曲。我觉得这是个人审美问题,也是因为我们浸润于不同的语言所致。我真的无意引发语言之争。但我觉得,Java的泛滥,助长浮躁的心态,而且喂饱了很多开培训班的人。

至于单一与多元之争,也无益。世界既需要多元也需要统一。我所指出的只是Java更为单极,而这单极,构造于Java代码所不能触及的底层。

fuliang 2009-05-05

此现象闻所未闻,到时写个程序验证一下

这个现象是由于C++粗糙的对继承的支持导致的,utensil同学没有听说过?

举个例子吧:

#include<iostream>  
using namespace std;  
  
class A{  
public:  
    void fun(int i){  
        cout << "fun(int i)" << endl;  
    }  
};  
  
class B: public A{  
public:  
    void fun(){  
        cout << "fun()" << endl;  
    }  
};  
  
int main(){  
       B b;  
       b.fun(1);//报错,找不到匹配的函数,fun()隐藏掉了继承而来的fun(int i)  
       return 0;  
}  

二流程序员自己不断误用,却不断怪在语言上

如果一个语言语法本身就有太多的陷阱,误用者我想不光是二流程序员吧,事实上windows和firefox早期版本一直存在很严重的内存泄露和其他有语言误用造成的一些bug,不能说微软和Mozilla的程序员都是二流水平吧。

使用C++的set的时候,也并不需要知道其实现细节,而且其使用也不应依赖于实现细节,而是如使用黑盒子一般使用其接口。

C++恰好混淆了这个问题,接口和实现没有分离。这样我使用普通的set的时候,你却暗地里不声不响的给我排好了序,我对此一无所知,结果又调用了一个快排重新排了一下,可知道快排对排好序的东西排序是一个最坏的复杂性。而Java则简单明了,我有Set接口,我有
SortedSet,TreeSet,HashSet等等这些实现,你想使用一个普通的Set,那好一个Set接口就好了,你想使用排好序的Set,那好SortSet吧。

我们不应该鼓励RTFM和RTFS的精神,而是变成了要鼓励Read the Fantastic Name的精神了?

为什么叫 Fantastic Name呢,一个好的代码,看到名字就知道干什么的了,一个还算可以的代码看文档就知道干什么的了,一个差的代码把代码全看一边就知道干什么了,一个最最最差的代码是把所有的东西都看完了,还是不知所云。

你那个概念缺失的说法,实在是一种教科书为了解释interface的存在而培养起来的你们java guy的莫名其妙的优越感。

在面向对象中is-a和contract的关系就像区分is-a和has-a的关系一样重要,如果你认为那只是教科书了解释interface的存在的理由的话,建议你因该了解更多的关于interface和Abstract class的关系。
本来没想和你争,但你说Java在面向对象的描述上非常的Ugly,我无法忍受,因为C++跟本没有资格在面向对象方面和Java说三到四。其实Java和C++面向的领域不同,Java主要面向高层的应用和建模,而C++则面向底层还想面向对象的一些应用。

utensil 2009-05-06

...报错,找不到匹配的函数,fun()隐藏掉了继承而来的...

又一个你对C++不熟悉的例子。你在原先指责这一点的时候,怪在多态和虚函数的实现身上,然而你的代码完全没有启用多态(没有用virtual)。再一次说明了,对语言没下功夫,没有真正熟悉,就开始用自己不熟悉的知识评判语言。

这个现象是C++中明确定义了的隐藏行为,是C++用编译器防止一些人为错误的设计哲学的产物之一。参见http://blog.csdn.net/zgbsoap/archive/2005/12/30/566120.aspx 中对重载和隐藏的不同的论述。

我之前的测试例子启用了多态,所以就编译无误。

....早期版本...

我还用说什么吗?早期的时候,从来就没有一流过的微软写出了臭名昭著的假C++框架MFC,至今仍有贻害。而Gecko的代码,我也阅读过,可惜啊,这样的代码质量严重限制了其在Mozilla自己的产品之外的运用。君不见大家(如Adobe Air)都宁可去包装WebKit,而不愿用Gecko吗?

...我使用普通的set的时候,你却暗地里不声不响的给我排好了序,我对此一无所知,结果又调用了一个快排重新排了一下...

set为了提供所保证的时间复杂度,通常用的是红黑树的实现,从其对<的依赖可见一般。

我不知道你用什么神奇的代码对set进行了快排!set的接口既未提供快排,如果用迭代器和STL算法,set的readonly的迭代器也是不允许这样做的。如果你是对迭代完了之后复制出来
数据进行的快排,那再一次你没有认真阅读set的迭代器的输出顺序相关的文档。

你恰恰没有仅仅就接口而使用,也没有按set的抽象概念去使用它。

建议你因该了解更多的关于interface和Abstract class的关系。

面向对象中的这些概念确实是重要的。然而Java用interface做的实现,却是拙劣的。我仅仅是认为这一点不能给Java任何优越性。C++中对同样的东西的更好的实现,是C++0x中的Concept。而且contract在编码中最重要的用途之一是在类似于trait和duck-typing之中的,而范型鸡肋的Java根本体会不到这个特性的真正用途。

本来没想和你争,但你说Java在面向对象的描述上非常的Ugly,我无法忍受,因为C++跟本没有资格在面向对象方面和Java说三到四。其实Java和C++面向的领域不同,Java主要面向高层的应用和建模,而C++则面向底层还想面向对象的一些应用。

我本来也无意于语言之争,但你说C++在面向对象的描述上非常的Ugly,我无法忍受,因为Java跟本没有资格在面向对象方面和C++说三到四。我已经尽量回退到说这是我们审美的不同这样把问题相对主义化的地方了,你却还要步步紧逼,说“Java主要面向高层的应用和建模”而我们只是半吊子,“还想面向对象”。事实上Java才是真正的半吊子,C++是建立在强类型的扎实基础上的泛类型语言,提供了最为完整的语言机制来表达面向对象的思想,而Java中胡乱的取舍和自我限制,限制了对OO的表达。

最后我想再说一次,Java的历史功勋是无疑的,但这应该归功于Java平台而非Java语言自身的设计,Java语言的自身设计是非常拙劣的。

fuliang 2009-05-06

又一个你对C++不熟悉的例子....我之前的测试例子启用了多态,所以就编译无误

我没有用到多态我为什么要启用virtual呢?我只是说明在继承的时候的诡异隐藏行为来说明C++在为追求效率,对继承进行一种粗糙的实现。

使用virtual编译无误? 你真的对C++很熟么,至少这点我不敢恭维。

utensil 2009-05-06

我没有用到多态我为什么要启用virtual呢?

但你在博客中却把这怪在了多态和虚函数身上。我主要是认为你并不了解出现这个现象的原因,胡乱地怪在虚函数身上,怪在C++的追求效率上。

有意而为之的隐藏行为是C++认真设计的作用域控制之一,并不是你所谓的粗糙。请看下面的程序。

使用virtual编译无误?

之前的测试代码用了以前写的代码的一部分,的确编译无误,但我今早在没重新看自己代码的情况下草草归结的原因错了,你的程序编译不过不是virtual的问题。

放一个重新写过的最小化的通过编译的程序吧:

#include <iostream>  
  
using namespace std;  
  
class A  
{  
    public:  
        void f(){ cout << "A's " << endl; }  
        void f(double, double) { cout << "A's" << endl;}  
};  
  
class B : public A  
{  
    public:  
               using A::f;  
        void f(int){ cout << "B's" << endl; }  
};  
  
int main()  
{  
    A * a = new B;  
    a->f();  
    a->f(1.0, 5.7);  
  
    B b;  
    b.f();  
    b.f(1.0, 5.7);  
}  

我承认现在我对Java已经不熟悉了。浸入一门语言并发现其拙劣之后,我就尽可能地忘记它给我带来的很多混乱的概念。实际写程序的时候需要Java的时候也很少。

我不能代表C++程序员,你也不能代表Java程序员。我们之间的争论,其实贻笑大方。Java源于C++,裁剪改造并不得体,却总是有高人一等的姿态。这是我从感情上非常抗拒的。我也不喜欢Java所培养程序员漠视底层的浮躁。看看C#,至少在语言中保留了通向底层的因素,也允许程序员书写不托管的代码。这些是我始终停不下争执的心因,偶尔语气冒犯像是人身攻击之处,其实更多的是指代浮躁、学艺不精却时常叫嚣的Java guy,it's not personal,还望海涵。

很高兴和你交流,其实这场论争也改变了我对Java的一些认识,也使我顺便复习了C++中一些知识。对C++某些知识不够熟悉是我学艺不精,不怪C++。我会继续在工作中熟悉C++的特性,并迎接改善了C++许多旧有弊端的C++0x。

宋皿

Published under (CC) BY-NC-ND tagged with cpp 随笔