« 玩了一下 ajax | 返回首页 | 生日 »

C 的回归

周末出差,去另一个城市给公司的一个项目解决点问题。回程去机场的路上,我用手机上 google reader 打发时间。第一眼就看到孟岩大大新的一篇:Linux之父话糙理不糙 。主题是 C 与 C++ 的语言之争。转到刘江的 blog 下读完了 Linux之父炮轰C++:糟糕程序员的垃圾语言 大呼过瘾。立刻把链接短信发给了几个朋友。

语言之争永远是火药味十足的话题。尤其是 C 和 C++ 的目标市场又有很高的重合性,C++ 程序员往往对C++ 其有着宗教般的虔诚。我想,今天我在自己的 blog 上继续这个战争,一定会换来更多的骂名。只不过这次 Linus 几句话真是说到我心坎里去了,不喊出来会憋坏的 :D

首先,给不熟悉我的朋友做一个技术背景的自我介绍:

我不是一个 Linux 的 fans ,虽然我今天对 Windows 也没有什么好感,但我的大部分工作还是在 WIndows 上做应用软件开发的,对 Windows 还算熟悉。现在我也用非 Windows 的系统,但那是一台 FreeBSD 的机器,不是 Linux 。

我自认为对 C++ 相当熟悉,精读过市面上能买到的关于 C++ 的大部分书籍,像 D&E of C++ 这样的经典还读了不只一遍。用 C++ 写过至少数十万行代码,阅读过 STL 的大部分源码,和 ACE / Boost 的一小部分。

曾经我是 C++ 的忠实粉丝,如果谁说 C++ 的不是,要么会选择跟他辩论到底,要么会对此人不屑一顾。

还有一点我认为非常重要:我第一次爱上 C++ 是 15 年前(1992 年),然后对其慢慢冷淡,回归 C 的怀抱。而到了 2000 年,我又一次爱上 C++ 。也就是说,从热爱 C++ 到否定它,在我的个人经历中,有过两次。不排除未来有第三次的可能,但这一点足可说明,否定 C++ 是出于一种理性的判断,而不是一种冲动。

写上这些,并非是想倚老卖老。我知道,想骂我的 C++ 程序员,更讨厌有人倚老卖老的数落 C++ 的不是。而且论资格,我顶多及的上 Linus 大大的一个零头,既然有老人在前撑腰,下面说话的底气就可以足一些了 :)


C 是 C++ 的一个子集(从 C99 开始已经不是了),用 C 能写出来的代码,C++ 一样可以写出来,然后可以完成的更好。

这是新手们自以为是的攻击武器。Linus 用了一个很恰当的理由做出反击:“你当然可以用任何语言编写糟糕的代码。但是,有些语言,尤其是带有一些心智(mental)包袱的语言本身就非常糟糕。”

没错,我最想说的就是这个。C++ 就是一个“带有一些心智(mental)包袱的语言”。这对软件设计的影响非常之大,没有经年的软件开发实践很难理解这一点。

从这一点上展开,把 ASM 和 C 比较的问题和 C 与 C++ 的比较相提并论就没有意义了。

接下来要找到的问题要点就是,C++ 比 C 多出来那些东西后,真的会带来心智包袱吗?这个问题不好回答。单纯从 C++ 语言特性的繁杂导致的不易掌握和误用这些角度是很难说服我自己的,更别说去说服那些比我聪明的多,刻苦的多的 C++ 程序员们。我自认为对所谓 C++ 的高级特性掌握的还是不错的,并运用在诸多实际项目中。他们相当有趣,在某种程度上也非常的有效。代码可以获得相当高的执行效率,并可以缩短编码的时间(更少的键击数),完成他们也有很大的成就感。

好了,让我再引用 Linus 的一句说到我心坎里的话。“字符串/内存管理根本无关紧要。这不是重要的部分,而且也不复杂。唯一真正重要的部分是设计。”

设计!这才是重中之重。

如果要说,这最近 10 年的程序员生涯我学会了什么?我认为,我比以前能设计出更好的代码了。能更准确的把握设计的坏味道。而对编程语言的掌握,对操作系统的熟悉,工作相关知识的了解等等。那些只是自然而然发生的事,那些是知识的积累,而非能力的提高。

“抽象”,“面向对象”,“设计模式”,这些重要吗?重要。对软件开发相当重要。但重要不是必要,执迷于“抽象”会使你离目标越来越远。当我们一次又一次的提取出事物的共性,建立起抽象层的时候,我们可能丢弃了真实。C++ 继承了 C 语言中“信任程序员”这一设计哲学,致力于让程序员在建立抽象层时,可以不做出额外的消耗。他的解决方式是提供尽可能多的语言工具和设计选择,任何一个都允许你在不用的时候不带来额外的性能损失。

这是一个美好的愿景:C++ 程序员指望可以建立强大的可复用的抽象层,面对世界上一切的具体应用。同时 CPU 执行序列在穿越这个坚厚的抽象层的过程中,居然可以以光速通过(通过抽象层没有额外的执行效率付出)。为此:C++ 社区创造了 STL ,创造了 Boost 。它们共同的关键词是:效率、复用。

再往上呢?另一个问题产生了:“——低效的抽象编程模型,可能在两年之后你会注意到有些抽象效果不怎么样,但是所有代码已经依赖于围绕它设计的‘漂亮’对象模型了,如果不重写应用程序,就无法改正。”这一段依旧是 Linus 语,我不停的引用,是因为我明白这一点,但是不能表达的更清楚。

使用 C++ 的程序员不断的强调复用性,却不断的需要重写代码。如果一段代码可以不被重写,那多半是因为对重写工程量的妥协。是的,其实我们可以用 C++ 的各种特性写出更好,更漂亮,更高效的代码。两年前的框架不那么完美,不是 C++ 语言的错,是两年前的我能力有限的缘故。但是因为需要改写的是设计框架,这意味着我们必须跟着变更已经完成的功能模块,或是加上桥接层。

的确,STL 和 Boost 都是世界顶尖程序员完成的。代码质量非常的高(当然,我对 Boost 的一部分持保留意见)。我不拿编译器兼容性和可移植性或是编译速度说事,虽然这些的确是现实问题,但不足以成为反对 C++ 基础类库的理由。

好好的用好 C++ 当然得用好 STL ,Boost 也应该认真考察一下。能够仔细读一下源码更好。合格的 C++ 程序员应该做这个。否则作为 C++ 程序员你就违背了 C++ 语言的设计哲学:C++ 信任了你,你就该对的起这种信任,搞清楚你写的每一行代码背后,机器都去干了什么。

但是,STL 过于庞大了,Boost 更加是。我不是抱怨阅读和学习它们的源码的难度和需要的时间和精力。正相反,我在学习它们的过程中充满了乐趣和感激之情。高手前辈透过这些高质量的代码教会了我很多东西。我隐隐担心的是,这么庞大的代码,它的设计不可能是永远正确的。两年之后,他们的设计肯定依旧正确,再两年还是的。但是我几乎敢肯定,放之更长远的时间来看,绝对会在某些设计领域发现其不是最佳的选择。到那一天,我们会选择修改吗?我想 C++ 社区会被迫选择妥协。但是,C++ 程序员心中会充满痛苦。

C 在这个问题上的抉择是不一样的。在效率问题上,C 程序里最令人担心的是函数调用的消耗。C++ 程序员最津津乐道的案例就是 std::sort 全面击败了 C 库中的 qsort 。C 语言的失败正在于多余的函数调用消耗。

但是,从一开始 C 就选择了承认函数调用的消耗,而这一点几乎是唯一。付出了这个代价后,设计失误导致的效率下降问题几乎总可以避免。C 和 C++ 都可以选择重写设计失败的部分,但不一样的是, C 程序员几乎可以不考虑妥协的问题。同样的是考虑极端效率的语言,C 语言坦然面对缺陷,才是真正的符合了 KISS 原则。

我对这个问题的见解,可以再引用 Linus 的一段话作为收场。“如果你想用更花哨的语言,C++绝对是最糟糕的选择。如果想要真正的高级特性,那就选择有垃圾回收或者好的系统集成的,而不是既缺乏C的简约(sparseness)又缺乏C的直接而且没有重要概念的高层绑定(high-level bindings to important concepts)的东西。”。这是我最近几年来一直坚持的观点:C++ 的发展,一定要补充对 GC 支持所需要的特性

强调一下,我并不讨厌 C++ :) 。 C++ 的粉丝们可以随便骂我,但是不要带上阶级仇恨。


ps. 最近两年多,我在做一个游戏引擎的项目。这个项目现在是第三个版本了。第一个版本是用 C++ 实现的,但是没有用任何已存在的类库(包括 STL)。在第二个版本中,我去掉了所有使用 C++ 高级特性实现的部分,只使用了 C++ 基本特性实现所有。今年重写的第三个版本,全部换成 C 代码了。这个项目的发展,可以反应出我个人对 C/C++ 理解的心路过程。

TrackBack

链入链接:C 的回归:

» C++不是万能的 from 猛禽的编程艺术
除了模式,上周日我和sunway还谈到了C++的问题——结果周二就看到Linus Torvalds就C++的话题跟人吵架,引发了国内C++圈里的大讨论——孟岩版,云风版,刘未鹏版。我也来扯一篇吧,不过层次肯定比这帮职业C++高手差多了。 [Read More]

» 看了一篇文章,c++的... from keledou
我的blog以前很长一段时间关注的都是C++中的技术&细节 [Read More]

» 学习C++:实践者的方法 from shawnshao
我的blog以前很长一段时间关注的都是C++中的技术&细节,乃至于读者和应者都寥寥。 [Read More]

» 转载与思考:用什么语言? from 汗水房1/3×1/2的大地编程
看到文章“Linux之父炮轰C++:糟糕程序员的垃圾语言”,觉得有一些道理,但是C++自然有其存在的意义... [Read More]

Comments

很赞同“唯一真正重要的部分是设计”这句话。 工作以后逐渐发现,那些技能性、通用性的知识(比如对编程语言的掌握,对操作系统的了解)其实是最容易获得的,几乎不需要什么成本。而跟特定领域相关的知识,只能在工作里一点点积累下来。所以招聘程序员的职位要求,几乎都需要受聘者拥有相关领域的工作经验。 这种“领域特定”的经验,于我看来,反映了一个程序员对该领域系统设计的体会与掌控能力。设计出好的系统,对于系统后续的维护和开发,是至关重要的。好的系统清晰,简单,一致,精巧,容易把握。 另外我觉得不必要对C++持有敌意态度,认为C++就是dirty的。在好的设计上,用好C++的一些特性,或许能比C更加精确地表达设计意图,毕竟C++的类型系统还是要强大一些。 不过要是考虑到ABI兼容性的话,还是老实用C好了,期待C还能继续进步。KISS还是很美的。
C++让人纠结
云风云风云风
嗯,怎么说,菜鸟不太理解这些,不过从C++近几年快速更新标准版本来说,C++在高级特性上落后了很久了;而C因为接近底层,很简洁。有人在武侠小说里说,武学理论有两条道路通向宗师,一条是由简至繁;另一条是由繁至简。C和C++的争端更像是这种繁简之争。但是,市场似乎更倾向于简洁,易用。所以ruby,python,这些易用的语言都出来了。
懂的人自然懂,C才是王道。 现在语言一大堆,各龙头公司都在开发新语言,都有自己的战略。 就是要搞一套与别人不同的。
写了那么多年c++,又反过来用c没必要,用c++写的成功游戏项目比比皆是。重点是写出了什么。而不是用什么语言写。程序设计并不是完美的东东,只是实现一件事件的一个手段。
虽然我目前不是一个程序员,也没有写过大量的代码,但是Linux和云风的观点我大致看懂了,相信我是一个无任何语言宗教的人。他们是在说在完成一个大项目的软件时,设计是第一位的。c++是的确可以在写这个项目的第一遍的时候会大幅降低代码量,和可维护度(我是说第一遍的维护度)。但是就像云风所说c++的STL和 Boost 是世界第一流的c++高手写出的,但是这个程序不可能永远适用,永远不修改。要是修改的时候,连写着个库的人恐怕也会妥协吧。C++所提倡的设计模式,也就是面对对象的各种方法一大堆,反正我不记得有多少种了。用c++写的代码的后果是什么呢,就是前后代码因为所谓的面向对象优秀特性,依赖度太高,等你发现你的代码落后了,想重构你会发现那简直是噩梦,改一行而动全身。想重构对不起,代价太大,你从心在来一遍也许会更合算....... C++赢在起跑线上的语言,确有必然输到未来的的特性。
过几年,这个结果都很清楚了,成功的都是c++写的游戏引擎
语音群呼www.huixun35.com
古老的帖子
真2
我不是高手,看了楼上很多人的发言也感觉收益很多。 很公正的说,C++比C好的地方就是抽象性和代码重用。所以C++的资源,可用库都比C多。 C相比C++的确简单了很多。 至于用什么,还是看个人吧。比较喜欢简洁有效的代码的用C,比较喜欢长远考虑,喜欢今天做的事对明天也有效果的,可以用C++。 另外还要看平台啊,给什么东西开发程序,就用相对的语言。单片啊,系统啊,嵌入啊基本上用C了,而Graphics啊,桌面开发啊的方面的东西还是C++的多吧,什么DirectX的
要判断谁好谁坏,打一架就知道了。
C++的根本错误是没有认识到现实世界中的事物是普遍联系并不断变化着的。 ‘蝴蝶效应’就说明对现实世界中的事物(包括程序)不是你封装了,就和别的事物没有联系了。
@freebsd, 恰恰相反,我在书里比较 C++ 比 C 编译出来的代码效率高,正是说明,如果你选择 C 而不是用 C++ ,绝对不是目标代码的性能这层因素。
在阁下的书里曾有一章是来说明C++是如何比C效率的,,不知道阁下还记得么。这种说完打自己脸的事,我是经常干,没想到阁下也和我一样。。
用C++的人大部分都有一个心智上的问题,以至会抵触,鄙视一些自己不喜欢的东西。儿子太顽皮,东搞搞西搞搞,爸爸教训一下不务正业的儿子,儿子就不认爸爸了,甚至要把爸爸消灭,爸爸也很生气,发誓要给儿子更严厉的教训.... 这是我看完这些消息的结论。 ============================== 眼光很锐利啊!
恩,去年我也参加了这场争论, 当时我是C++的忠实FANS, 好像我的CSDN留言还成为了C++帮的第一个,呵呵。 现在发现应该保持中立了。 C++确实有点让人尴尬。 学的时候很带劲,用的时候就有点郁闷了。 现在的目标是学好C++,用好C.
放不下所以选择回归
抽象很容易掩盖很多已存在的关系,而这些关系很有可能使产品做得更好,尤其在性能方面。 -------------------------- 举个简单的例子:(1)system -> (*)processor 客户程序需要列出产品中所有的processor名,供用户选择,然后调用相应的processor功能。 设计1:c++设计的时候很容易就想到了抽象出processor概念,交互时客户程序得到的是一个IProcessor*。 设计2:c设计时则很容易形如: int get_num_procs(); char* get_proc_name(int proc_id); -〉这两个设计有一个差别,对象默认指向的是一段连续空间,而ID(或句柄)指向的则是一段离散空间。 设计1结果可能会导致诸如客户程序列名字时将创建整个对象等问题(虽然可以使用proxy将空间切割)。 而最后会发现,设计2将更易于修改,而用c的方式(c++当然也可以使用该方式)写出设计2的代码将更简约。
话题已经贴近CTO之类的群体,扯得太远了。碰到这类话题忍不住就臆想和唠叨了这么多,实在不好意思。走了~
发现一篇blog文,文中对语言的理解也有自己的一番看法。 http://canonical.javaeye.com/blog/147064
按9楼的说法,用虚拟机来保持恒定的场景,我感觉还不够。如果再联合UML一起的话……感觉这样才是语言发展的方向。
啊~ 还连带想起了某人的观点:知识件! 将知识从计算机软件中解放出来,软件工程师提供软件平台,而知识工程师提供知识模块……
忽然想到范畴论,这种抽象数学似乎也可以作为发展计算机语言的理论基础~
呵呵,现在发现9楼和52楼的观点很闪亮,Knuth相关著作是再拿起的时机了,顺便翻翻形式语义学。
只有完全没有二义性,才能真正达到“复用”,而只在某种程度上没有二义性,那么脱离该程度,就难以复用了。 弄懂细节,就是为了不产生二义性。语言在该程度上细节如此之多,则语言在该程度上的二义性也就非常多,那么可以说它失败了么?
最后: 你认为,代码是写给人看的,还是机器看的呢? 代码能不能有二义性呢? 语言的发展需要新的词汇,而不同场景对相同词汇的解释自然会有不同。 就算是c,它在不同编译器上的解释也会如此。 也许,语言的发展必须依赖一个恒定的场景,有或者计算机语言公式还未成熟。 也许,c++…… ps:离散数学~看来我又要端起它了…… 8~
这一次长考学到很多,多谢。 ps: lisp么~ 唉,计算机是替人做事的,没有心的情况下,死物毕竟是死物,即使告诉它这是什么样的世界……
垃圾回收的历史比 C 语言长的多。 lisp 已经有超过 50 年的历史了。
c的黄金时代有垃圾回收概念么?
设计灵感可以来源于生活,用c++可以带来这方面的灵感(产品里可以建立垃圾回收站,也可以出现出租屋)。c的设计灵感来源于哪里?
呵呵,再胡扯一句:想把对象“杀死”,自然会有意外发生。而目前的各种“杀死”对象的方法和当今世界某些情况不是很相似么~
呵呵,c与c++的根本区别在于设计理念不同,当真正明了语言的设计理念和产品的设计理念时,自然而然会做出正确的选择。 ps:我真正明了了么?我只认为,c++,全局函数体是一个场所(景),一个让对象们表演的舞台(世界?宇宙?)。 当然,对象必须都是“活”的(你想让他自然死亡,还是被其他对象杀死?你真正的把int当作“活”物来看待了么?)。
最后一提,c++的main与c的main完全不同。
当然,也可以选择只与处理器交流,(C存在的根本就是如此)。但是,我还是想创造……
孩子们在这个世界中是什么样的存在?将他们描述出来才是语言的真意。那么,你是想写记叙文、议论文还是散文呢?
我只知道我要的是创造,而不是模拟这个世界。
语言紧紧是一种实现逻辑的工具而已...工具当然有好坏之分,但好坏之分不是绝对的而是相对的,实现某种逻辑而选择什么样的工具只能根据实际出发.邓小平理论明写着的"实事求事",还有一句叫做"走具有中国特色的社会主义道路",写到这份上了,应该明白乍回事了.
to Wesley: 首先道歉。我兴奋起来的时候就会说胡话,会变成疯狗咬人。对不起。 然后对于你的具体问题。小规模、不涉及架构的代码复用,我想了一下,还真是用模板来解决最好, C 的话就用宏也行得通。
Hello, nice site :)
C 的另一个可能的优势就是缺乏一些自动机制。 比如 C++ 可以自动调用构造函数、析构函数,可以跨越多层调用栈的异常。而 C 都做不到。 前者要求程序员写明构造顺序,后者要求程序员必须在每一层检测错误。这些做法,可以避免无意的行为,对于谨慎的程序员来说,就更可能控制代码质量。
学习跟做项目是不同的。学习本身不能带有功利性。了解各个方面才能抓住本质。 我一直觉得编程、设计软件需要悟性,它不是天生具来的,不是靠别人说什么是正确的什么是错误的就可以减少获得它的难度。学习过程中,经历别人口中所谓正确的路和错误的路同等重要。 因为学习没有捷径,思考的历程才是唯一的重点。
对于初学者很迷茫,难道现在不该学习c++了 还是应该做c更适合?
高手如云,呵呵。 写文章,我还想回归到文言文呢! 可惜从小就学现代文,文言文对我实在是太难了,所以只好将就了。
重要的是思想和设计 什么语言都是小问题
继承关系是一种耦合度很高的关系,它与组合及一般化(genericity)一样,提供了OO中的一种基本方法,用以将不同的软件组件组合起来。一个类的实例同时也是那个类的所有的祖先的实例。为了保证面向对象设计的有效性,我们应该保存下这种关系的一致性。在子类中的每一次重新定义都应该与在其祖先类中的最初定义进行一致性检查。子类中应该保存下其祖先类的需求。如果存在着不能被保存的需求,就说明了系统的设计有错误,或者是在系统中此处使用继承是不恰当的。由于继承是面向对象设计的基础,所以才会要求有一致性检测。C++中对于非虚拟函数重载的实现, 意味着编译器将不会为其进行一致性检测。C++并没有提供面向对象设计的这方面的保证。 继承被分成"语法"继承和"语义"继承两部分。Saake等人将其描述如下:"语法继承表示为结构或方法定义的继承,并且因此与代码的重复使用(以及重写被继承方法的代码)联系起来。语义继承表示为对对象语义(即对象自己)的继承,。这种继承形式可以从语义的数据模型中被得知,在此它被用于代表在一个应用程序的若干个角色中出现的一个对象。"[SJE 91]。Saake等人集中研究了继承的语义形式。通过是行为还是语义的继承方式的判断,表示了对象在系统中所扮的角色。 然而, Wegner相信代码继承更具有实际的价值。他将语法与语义继承之间的区别表示为代码和行为上的区别[Weg 91](p43)。他认为这样的划分不会引起一方与另一方的兼容,并且还经常与另一方不一致。Wegner同样也提出这样的问题:"应该怎样抑制对继承属性的修改?"代码继承为模块化(modularisation)提供一个基础。行为继承则依赖于"is-a"关系。这两种继承方式在合适处都十分有用。它们都要求进行一致性的检测,这与实际上的有意义的继承密不可分。 看起来在语义保持关系中那些限制最多的形式中,继承似乎是其中最强的形式;子类应该保存祖先类中的所有假设。 Meyer [Meyer 96a and 96b]也对继承技术进行了分类。在他的分类法中,他指出了继承的12种用法。这些分析也给我们怎么使用继承提供了一个很好的判断标准,如:什么时候应该使用继承,什么时候不应该它。 软件组件就象七巧板一样。当我们组装七巧板时,每一块板的形状必须要合适,但更重要地是,最终拼出的图像必须要有意义,能够被说得通。而将软件组件组合起来就更困难了。七巧板只是需要将原本是完整的一幅图像重新组合起来。而对软件组件的组合会得到什么样的结果,是我们不可能预见到的。更糟的是,七巧板的每一块通常是由不同的程序员产生的,这样当整个的系统被组合起来时,对于它们的吻合程度的要求就更高了。 C++中的继承像是一块七巧板,所有的板块都能够组合在一起,但是编译器却没有办法检测最终的结果是否有意义。换句话说,C++仅为类和继承提供了语法,而非语义。可重用的C++函数库的缓慢出现,暗示了C++可能会尽可能地不支持可重用性。相反的是,Java,Eiffel和Object Pascal都与函数库包装在一起出现。Object Pascal与MacApp应用软件框架联系非常紧密。Java也从与Java API的耦合中解脱出来,取而代之的是一个包容广泛的函数库。Eiffel也同样是与一个极其全面的函数库集成在一起,该函数库甚至比Java的还要大。事实上函数库的概念已经成为一个优先于Eiffel语言本身的工程,用以对所有在计算机科学中通用的结构进行重新分类,得到一个常用的分类法。 [Meyer 94].
函数重载 C++允许在参数类型不同的前提下重载函数。重载的函数与具有多态性的函数(即虚函数)不同处在于:调用正确的被重载函数实体是在编译期间就被决定了的;而对于具有多态性的函数来说,是通过运行期间的动态绑定来调用我们想调用的那个函数实体。多态性是通过重定义(或重写)这种方式达成的。请不要被重载(overloading)和重写(overriding)所迷惑。重载是发生在两个或者是更多的函数具有相同的名字的情况下。区分它们的办法是通过检测它们的参数个数或者类型来实现的。重载与CLOS中的多重分发(multiple dispatching)不同,对于参数的多重分发是在运行期间多态完成的。 【Reade 89】中指出了重载与多态之间的不同。重载意味着在相同的上下文中使用相同的名字代替出不同的函数实体(它们之间具有完全不同的定义和参数类型)。多态则只具有一个定义体,并且所有的类型都是由一种最基本的类型派生出的子类型。C. Strachey指出,多态是一种参数化的多态,而重载则是一种特殊的多态。用以判断不同的重载函数的机制就是函数标示(function signature)。 重载在下面的例子中显得很有用: max( int, int ) max( real, real ) 这将确保相对于类型int和real的最佳的max函数实体被调用。但是,面向对象的程序设计为该函数提供了一个变量,对象本身被被当作一个隐藏的参数传递给了函数(在C++中,我们把它称为this)。由于这样,在面向对象的概念中又隐式地包含了一种对等的但却更有更多限制的形式。对于上述讨论的一个简单例子如下: int i, j; real r, s; i.max(j); r.max(s); 但如果我们这样写:i.max(r),或是r.max(j),编译器将会告诉我们在这其中存在着类型不匹配的错误。当然,通过重载运算符的操作,这样的行为是可以被更好地表达如下: i max j 或者 r max s 但是,min和max都是特殊的函数,它们可以接受两个或者更多的同一类型的参数,并且还可以作用在任意长度的数组上。因此,在Eiffel中,对于这种情况最常见的代码形式看起来就像这样: il:COMPARABLE_LIST[INTEGER] rl:COMPARABLE_LIST[REAL] i := il.max r := rl.max 上面的例子显示,面向对象的编程典范(paradigm),特别是和范型化(genericity)结合在一起时,也可以达到函数重载的效果而不需要C+ +中的函数重载那样的声明形式。然而是C++使得这种概念更加一般化。C++这样作的好处在于,我们可以通过不止一个的参数来达到重载的目的,而不是仅使用一个隐藏的当前对象作为参数这样的形式。 另外一个我们需要考虑的因素是,决定(resolved)哪个重载函数被调用是在编译阶段完成的事情,但对于重写来说则推后到了运行期间。这样看起来好像重载能够使我们获得更多性能上的好处。然而,在全局分析的过程中编译器可以检测函数min 和max是否处在继承的最末端,然后就可以直接的调用它们(如果是的话)。这也就是说,编译器检查到了对象i和r,然后分析对应于它们的max函数,发现在这种情况下没有任何多态性被包含在内,于是就为上面的语句产生了直接调用max的目标代码。与此相反的是,如果对象n被定义为一个NUMBER, NUMBER又提供一个抽象的max函数声明(我们所用的REAL.max和INTERGER.max都是从它继承来的),那么编译器将会为此产生动态绑定的代码。这是因为n既可能是INTEGER,也有可能是REAL。 现在你是不是觉得C++的这种方法(即通过提供不同的参数来实现函数的重载)很有用?不过你还必须明白,面向对象的程序设计对此有着种种的限制,存在着许多的规则。C++是通过指定参数必须与基类相符合的方式实现它的。传入函数中的参数只能是基类,或是基类的派生类。例如: A.f( B someB ) class B ...; class D : public B ...; A a; D d; a.f( d ); 其中d必须与类'B'相符,编译器会检测这些。 通过不同的函数签名(signature)来实现函数重载的另一种可行的方法是,给不同的函数以不同的名字,以此来使得它们的签名不同。我们应该使用名字来作为区分不同实体(entities)的基础。编译器可以交叉检测我们提供的实参是否符合于指定的函数需要的形参。这同时也导致了软件更好的自记录(self-document)。从相似的名字选择出一个给指定的实体通常都不会很容易,但它的好处确实值得我们这样去做。 [Wiener95]中提供了一个例子用以展示重载虚拟函数可能出现的问题: class Parent { public: virutal int doIt( int v ) { return v * v; } }; class Child: public Parent { public: int doIt( int v, int av = 20 ) { return v * av; } }; int main() { int i; Parent *p = new Child(); i = p->doIt(3); return 0; } 当程序执行完后i会等于多少呢?有人可能会认为是60,然而结果却是9。这是因为在Child中doIt的签名与在Parent中的不一致,它并没有重写Parent中的doIt,而仅仅是重载了它,在这种情况下,缺省值没有任何作用。 Java也提供了方法重载,不同的方法可以拥有同样的名字及不同的签名。 在Eiffel中没有引入新的技术,而是使用范型化、继承及重定义等。Eiffel提供了协变式的签名方式,这意味着在子类的函数中不需要完全符合父类中的签名,但是通过Eiffel的强类型检测技术可以使得它们彼此相匹配。
保证类型安全的联结属性(type-safe linkage) C++ARM中解释说type-safe linkage并不能100%的保证类型安全。既然它不那100%的保证类型安全,那么它就肯定是不安全的。统计分析显示:即便在很苛刻的情况下,C++ 出现单独的O-ring错误的可能性也只有0.3%。但我们一旦将6种这样的可能导致出错的情况联合起来放在一起,出错的几率就变得大为可观了。在软件中,我们经常能够看到一些错误的起因就是其怪异的联合。OO的一个主要目的就是要减少这种奇怪的联合出现。 大多数问题的起因都是一些难以察觉的错误,而不是那些简单明了的错误导致问题的产生。而且在通常的情况下,不到真正的临界时期,这样的错误一般都很难被检测到,但我们不能由此就低估了这种情况的严肃性。有许多的计划都依赖于其操作的正确性,如太空计划、财政结算等。在这些计划中采用不安全的解决方案是一种不负责任的做法,我们应该严厉禁止类似情况的出现。 C++在type-safe linkage上相对于C来说有了巨大的进步。在C中,链接器可以将一个带有参数的诸如f(p1,...)这样的函数链接到任意的函数f()上面,而这个 f()甚至可以没有参数或是带有不同的参数都行。这将会导致程序在运行时出错。由于C++的type-safe linkage机制是一种在链接器上实做的技巧,对于这样的不一致性,C++将统统拒绝。 C++ARM将这样的情况概括如下--“处理所有的不一致性->这将使得C++得以100%的保证类型安全->这将要求对链接器的支持或是机制(环境)能够允许编译器访问在其他编译单元里面的信息”。 那么为什么市面上的C++编译器(至少AT&T的是如此)不提供访问其他毕业单元中的信息的能力呢?为什么到现在也没有一种特殊的专门为C++设计的链接器出现,可以100%的保证类型安全呢?答案是C++缺乏一种全局分析的能力(在上一节中我们讨论过)。另外,在已有的程序组件外构造我们的系统已经是一种通用的Unix软件开发方式,这实现了一定的重用,然而它并不能为面向对象方式的重用提供真正的弹性及一致性。 在将来, Unix可能会被面向对象的操作系统给替代,这样的操作系统足够的“开放”并且能够被合适地裁剪用以符合我们的需求。通过使用管道(pipe)及标志 (flag),Unix下的软件组件可以被重复利用以提供所需的近似功能。这种方法在一定的情况下行之有效,并且颇负效率(如小型的内部应用,或是用以进行快速原型研究),但对于大规模、昂贵的、或是对于安全性要求很高的应用来说,采取这样的开发方法就不再适合了。在过去的十年中,集成的软件(即不采用外部组件开发的软件)的优点已经得到了认同。传统的Unix系统不能提供这样的优点。相比而言,集成的系统更加的复杂,对于开发它们的开发人员有着更多的要求,但是最终用户(end user)要求的就是这样的软件。将所有的东西拙劣的放置于一起构成的系统是不可接受的。现在,软件开发的重心已经转到组件式软件开发上面来了,如公共领域的OpenDoc或是Microsoft的OLE。 对于链接来说,更进一步的问题出现在:不同的编译单元和链接系统可能会使用不同的名字编码方式。这个问题和type-safe linkage有关,不过我们将会在“重用性及兼容性”这节讲述之。 Java使用了一种不同的动态链接机制,这种机制被设计的很好,没有使用到Unix的链接器。Eiffel则不依赖于Unix或是其他平台上的链接器来检测这些问题,一切都由编译器完成。 Eiffel 定义了一种系统层上的有效性(system-level validity)。一个Eiffel编译器也就因此需要进行封闭环境下的分析,而不是依赖于链接器上的技巧。你也可以就此认为Eiffel程序能够保证 100%的类型安全。对于Eiffel来说有一个缺点就是,编译器需要干的事情太多了。(通常我们会说的是它太“慢”了,但这不够精确)目前我们可以通过对于Eiffel提供一定的扩展来解决这个问题,如融冰技术(melting-ice technology),它可以使得我们对于系统的改动和测试可以在不需要每次都进行重新编译的情况下进行。 现在让我们来概括一下前两个小节 - 有两个原因使我们需要进行全局(或封闭环境下的)分析:一致性检测及优化。这样做可以减掉程序员身上大量的负担,而缺乏它是C++中的一个很大的不足。
全局分析 【P&S 94】中提到对于类型安全的检测来说有两种假设。一种是封闭式环境下的假设,此时程序中的各个部分在编译期间就能被确定,然后我们可以对于整个程序来进行类型检测。另一种是开放式环境下的假设,此时对于类型的检测是在单独的模块中进行的。对于实际开发和建立原型来说,第二种假设显得十分有效。然而,【P&S 94】中又提到,“当一种已经完成的软件产品到达了成熟期时,采用封闭式环境下的假设就可以被考虑了,因为这样可以使得一些比较高级的编译技术得以有了用武之处。只有在整个程序都被了解的情况下,我们才可能在其上面执行诸如全局寄存器分配、程序流程分析及无效代码检测等动作。”(附:【P&S 94】Jens Palsberg and Michael I. Schwartzbach, Object-Oriented Type Systems, Wiley 1994) C++中的一个主要问题就是:对于程序的分析过程被编译器(工作于开放式环境下的假设)和链接器(依赖于十分有限的封闭式环境下的分析)给划分开了。封闭式环境下的或是全局的分析被采用的实质原因有两个方面:首先,它可以保证汇编系统的一致性;其次,它通过提供自动优化,减轻了程序员的负担。 程序员能够被减轻的主要负担是:设计父类的程序员不再需要(不得不)通过利用虚拟函数的修饰成份(virtual),来协助编译器建立起vtable。正如我们在“虚拟函数”中所说,这样做将会影响到软件的弹性。Vtable不应该在一个单独的类被编译时就被建立起来,最好是在整个系统被装配在一起时一并被建立。在系统被装配(链接)时期,编译器和链接器协同起来,就可以完全决定一个函数是否需要在vtable中占有一席之地。除上述之外,程序员还可以自由地使用在其他模块中定义的一些在本地不可见的信息;并且程序员不再需要维护头文件的存在了。 在Eiffel和Object Pascal中,全局分析被应用于整个系统中,决定真正的多态性的函数调用,并且构造所需的vtable。在Eiffel中,这些是由编译器完成的。在 Object Pascal中,Apple扩展了链接器的功能,使之具有全局分析的能力。这样的全局分析在C/Unix环境下很难被实现,所以在C++中,它也没有被包含进去,使得负担被留给了程序员。 为了将这个负担从程序员身上移除,我们应该将全局分析的功能内置于链接器中。然而,由于C++一开始的版本是作为一个Cfront预处理器实现的,对于链接器所做的任何必要的改动不能得到保证。C++的最初实现版本看起来就像一个拼凑起来的东西,到处充满着漏洞。【译者认为:这也太过分了吧:)】C++的设计严格地受限于其实现技术,而不是其他(例如没有采用好的程序语言设计原理等),因为那样就需要新的编译器和链接器了。也就是说,现在的C++发展严格地受限于其最初的试验性质的产品。 我现在确信这种技术上的依赖关系(即C++ 依赖于早先的C)严重地损害了C++,使之不是一个完整意义上的面向对象的高级语言。一个高级语言可以将簿记工作从程序员身上接手过去,交给编译器去完成,这也是高级语言的主要目的。缺乏全局(或是封闭式环境下的)分析是C++的一个主要不足,这使得C++在和Eiffel之类的语言相比时显得十分地不足。由于Eiffel坚持系统层次上的有效性及全局分析,这意味着Eiffel要比C++显得有雄心多了,但这也是Eiffel产品为什么出现地这么缓慢的主要原因。 Java只有在需要时才动态地载入软件的部分,并将它们链接起来成为一个可以运行的系统。也因而使得静态的编译期间的全局分析变成不可能的了(因为Java被设计成为一个动态的语言)。然而,Java假设所有的方法都是virtual的,这也就是为什么Java和 Eiffel是完全不同的工具的一个原因。关于Eiffel,可以参见于Dynamic Linking in Eiffel(DLE)
虚拟函数 在所有对C++的批评中,虚拟函数这一部分是最复杂的。这主要是由于C++中复杂的机制所引起的。虽然本篇文章认为多态(polymorphism)是实现面向对象编程(OOP)的关键特性,但还是请你不要对此观点(即虚拟函数机制是C++中的一大败笔)感到有什么不安,继续看下去,如果你仅仅想知道一个大概的话,那么你也可以跳过此节。【译者注:建议大家还是看看这节会比较好】 在C++中,当子类改写/重定义(override/redefine)了在父类中定义了的函数时,关键字virtual使得该函数具有了多态性,但是virtual关键字也并不是必不可少的(只要在父类中被定义一次就行了)。编译器通过产生动态分配(dynamic dispatch)的方式来实现真正的多态函数调用。 这样,在C++中,问题就产生了:如果设计父类的人员不能预见到子类可能会改写哪个函数,那么子类就不能使得这个函数具有多态性。这对于C++来说是一个很严重的缺陷,因为它减少了软件组件(software components)的弹性(flexibility),从而使得写出可重用及可扩展的函数库也变得困难起来。 C++同时也允许函数的重载(overload),在这种情况下,编译器通过传入的参数来进行正确的函数调用。在函数调用时所引用的实参类型必须吻合被重载的函数组(overloaded functions)中某一个函数的形参类型。重载函数与重写函数(具有多态性的函数)的不同之处在于:重载函数的调用是在编译期间就被决定了,而重写函数的调用则是在运行期间被决定的。 当一个父类被设计出来时,程序员只能猜测子类可能会重载/重写哪个函数。子类可以随时重载任何一个函数,但这种机制并不是多态。为了实现多态,设计父类的程序员必须指定一个函数为virtual,这样会告诉编译器在类的跳转表(class jump table)【译者窃以为是vtable,即虚拟函数入口表】中建立一个分发入口。于是,对于决定什么事情是由编译器自动完成,或是由其他语言的编译器自动完成这个重任就放到了程序员的肩上。这些都是从最初的C++的实现中继承下来的,而和一些特定的编译器及联结器无关。 对于重写,我们有着三种不同的选择,分别对应于:“千万别”,“可以”及“一定要”重写: 1、重写一个函数是被禁止的。子类必须使用已有的函数 2、函数可以被重写。子类可以使用已有的函数,也可以使用自己写的函数,前提是这个函数必须遵循最初的界面定义,而且实现的功能尽可能的少及完善 3、函数是一个抽象的函数。对于该函数没有提供任何的实现,每个子类都必须提供其各自的实现 父类的设计者必须要决定1和3中的函数,而子类的设计者只需要考虑2就行了。对于这些选择,程序语言必须要提供直接的语法支持。 选项1、 C ++并不能禁止在子类中重写一个函数。即使是被声明为private virtual的函数也可以被重写。【Sakkinen92】中指出了即使在通过其他方法都不能访问到private virtual函数,子类也可以对其进行重写。【译者注:Sakkinen92我也没看过,但经我简单的测试,确实可以在子类中重写父类中的 private virtual函数】 实现这种选择的唯一方法就是不要使用虚拟函数,但是这样的话,函数就等于整个被替换掉了。首先,函数可能会在无意中被子类的函数给替换掉。在同一个scope中重新宣告一个函数将会导致名字冲突(name clash);编译器将会就此报告出一个“duplicate declaration”的语法错误。允许两个拥有同名的实体存在于同一个scope中将会导致语义的二义性(ambiguity)及其他问题(可参见于 name overloading这节)。 下面的例子阐明了第二个问题: class A { public: void nonvirt(); virtual void virt(); }; class B : public A { public: void nonvirt(); void virt(); }; A a; B b; A *ap = &b; B *bp = &b; bp->nonvirt(); file://calls B::nonvirt as you would expect ap->nonvirt(); file://calls A::nonvirt even though this object is of type B ap->virt(); file://calls B::virt, the correct version of the routine for B objects 在这个例子里,B扩展或替换掉了A中的函数。B::nonvirt是应该被B的对象调用的函数。在此处我们必须指出,C++给客户端程序员(即使用我们这套继承体系架构的程序员)足够的弹性来调用A::nonvirt或是B::nonvirt,但我们也可以提供一种更简单,更直接的方式:提供给A:: nonvirt和B::nonvirt不同的名字。这可以使得程序员能够正确地,显式地调用想要调用的函数,而不是陷入了上面的那种晦涩的,容易导致错误的陷阱中去。具体方法如下: class B: public A { public: void b_nonvirt(); void virt(); } B b; B *bp = &b; bp->nonvirt(); file://calls A::nonvirt bp->b_nonvirt(); file://calls B::b_nonvirt 现在,B的设计者就可以直接的操纵B的接口了。程序要求B的客户端(即调用B的代码)能够同时调用A::nonvirt和B::nonvirt,这点我们也做到了。就Object-Oriented Design(OOD)来说,这是一个不错的做法,因为它提供了健壮的接口定义(strongly defined interface)【译者认为:即不会引起调用歧义的接口】。C++允许客户端程序员在类的接口处卖弄他们的技巧,借以对类进行扩展。在上例中所出现的就是设计B的程序员不能阻止其他程序员调用A::nonvirt。类B的对象拥有它们自己的nonvirt,但是即便如此,B的设计者也不能保证通过B的接口就一定能调用到正确版本的nonvirt。 C++同样不能阻止系统中对其他处的改动不会影响到B。假设我们需要写一个类C,在C 中我们要求nonvirt是一个虚拟的函数。于是我们就必须回到A中将nonvirt改为虚拟的。但这又将使得我们对于B::nonvirt所玩弄的技巧又失去了作用(想想看,为什么:D)。对于C需要一个virtual的需求(将已有的nonvirtual改为virtual)使得我们改变了父类,这又使得所有从父类继承下来的子类也相应地有了改变。这已经违背了OOP拥有低耦合的类的理由,新的需求,改动应该只产生局部的影响,而不是改变系统中其他地方,从而潜在地破坏了系统的已有部分。 另一个问题是,同样的一条语句必须一直保持着同样的语义。例如:对于诸如a->f()这样的多态性语句的解释,系统调用的是由最符合a所真正指向类型的那个f(),而不管对象的类型到底是A,还是A的子类。然而,对于C++的程序员来说,他们必须要清楚地了解当f()被定义成virtual或是non-virtual时,a->f()的真正涵义。所以,语句a->f()不能独立于其实现,而且隐藏的实现原理也不是一成不变的。对于f()的宣告的一次改变将会相应地改变调用它时的语义。与实现独立意味着对于实现的改变不会改变语句的语义,或是执行的语义。 如果在宣告中的改变导致相应的语义的改变,编译器应该能检测到错误的产生。程序员应该在宣告被改变的情况下保持语义的不变。这反映了软件开发中的动态特性,在其中你将能发现程序文本的永久改变。 其他另一个与a->f()相应的,语义不能被保持不变的例子是:构造函数(可参考于C++ ARM, section 10.9c, p 232)。而Eiffel和Java则不存在这样的问题。它们中所采用的机制简单而又清晰,不会导致C++中所产生的那些令人吃惊的现象。在Java中,所有的一起都是虚拟的,为了让一个方法【译者注:对应于C++的函数】不能被重写,我们可以用final修饰符来修饰这个方法。 Eiffel允许程序员指定一个函数为frozen,在这种情况下,这个函数就不能在子类中被重写。 选项2、 是使用现有的函数还是重写一个,这应该是由撰写子类的程序员所决定的。在C++中,要想拥有这种能力则必须在父类中指定为virtual。对于OOD来说,你所决定不想作的与你所决定想作的同样重要,你的决定应该是越迟下越好。这种策略可以避免错误在系统前期就被包含进去。你作决定越早,你就越有可能被以后所证明是错误的假设所包围;或是你所作的假设在一种情况下是正确的,然而在另一种情况下却会出错,从而使得你所写出来的软件比较脆弱,不具有重用性(reusable)【译者注:软件的可重用性对于软件来说是一个很重要的特性,具体可以参考《Object-Oriented Software Construct》中对于软件的外部特性的叙述,P7, Reusability, Charpter 1.2 A REVIEW OF EXTERNAL FACTORS】。 C++要求我们在父类中就要指定可能的多态性(这可以通过virtual来指定),当然我们也可以在继承链中的中间的类导入virtual机制,从而预先判断某个函数是否可以在子类中被重定义。这种做法将导致问题的出现:如那些并非真正多态的函数(not actually polymorphic)也必须通过效率较低的table技术来被调用,而不像直接调用那个函数来的高效【译者注:在文章的上下文中并没有出现not actually polymorphic特性的确切定义,根据我的理解,应该是声明为polymorphic,而实际上的动作并没能体现polymorphic这样的一种特性】。虽然这样做并不会引起大量的花费(overhead),但我们知道,在OO程序中经常会出现使用大量的、短小的、目标单一明确的函数,如果将所有这些都累计下来,也会导致一个相当可观的花费。C++中的政策是这样的:需要被重定义的函数必须被声明为virtual。糟糕的是,C++同时也说了, non-virtual函数不能被重定义,这使得设计使用子类的程序员就无法对于这些函数拥有自己的控制权。【译者注:原作中此句显得有待推敲,原文是这样写的:it says that non-virtual routines cannot be redefined, 我猜测作者想表达的意思应该是:If you have defined a non-virtual routine in base, then it cannot be virtual in the base whether you redefined it as virtual in descendant.】 Rumbaugh等人对于C++中的虚拟机制的批评如下:C++拥有了简单实现继承及动态方法调用的特性,但一个C++的数据结构并不能自动成为面向对象的。方法调用决议(method resolution)以及在子类中重写一个函数操作的前提必须是这个函数/方法已经在父类中被声明为virtual。也就是说,必须在最初的类中我们就能预见到一个函数是否需要被重写。不幸的是,类的撰写者可能不会预期到需要定义一个特殊的子类,也可能不会知道那些操作将要在子类中被重写。这意味着当子类被定义时,我们经常需要回过头去修改我们的父类,并且使得对于通过创建子类来重用已有的库的限制极为严格,尤其是当这个库的源代码不能被获得是更是如此。(当然,你也可以将所有的操作都定义为virtual,并愿意为此付出一些小小的内存花费用于函数调用)【RBPEL91】 然而,让程序员来处理virtual是一个错误的机制。编译器应该能够检测到多态,并为此产生所必须的、潜在的实现virtual的代码。让程序员来决定 virtual与否对于程序员来说是增加了一个簿记工作的负担。这也就是为什么C++只能算是一种弱的面向对象语言(weak object-oriented language):因为程序员必须时刻注意着一些底层的细节(low level details),而这些本来可以由编译器自动处理的。 在C++中的另一个问题是错误的重写(mistaken overriding),父类中的函数可以在毫不知情的情况下被重写。编译器应该对于同一个名字空间中的重定义报错,除非编写子类的程序员指出他是有意这么做的(即对于虚函数的重写)。我们可以使用同一个名字,但是程序员必须清楚自己在干什么,并且显式地声明它,尤其是在将自己的程序与已经存在的程序组件组装成新的系统的情况下更要如此。除非程序员显式地重写已有的虚函数,否则编译器必须要给我们报告出现了名字被声明多处(duplicate declaration)的错误。然而,C++却采用了Simula最初的做法,而这种方法到现在已经得到了改良。其他的一些程序语言通过采用了更好的、更加显式的方法,避免了错误重定义的出现。 解决方法就是virtual不应该在父类中就被指定好。当我们需要运行时的动态绑定时,我们就在子类中指定需要对某个函数进行重写。这样做的好处在于:对于具有多态性的函数,编译器可以检测其函数签名(function signature)的一致性;而对于重载的函数,其函数签名在某些方面本来就不一样。第二个好处表现在,在程序的维护阶段,能够清楚地表达程序的最初意愿。而实际上后来的程序员却经常要猜测先前的程序员是不是犯了什么错误,选择一个相同的名字,还是他本来就想重载这个函数。 在 Java中,没有virtual这个关键字,所有的方法在底层都是多态的。当方法被定义为static, private或是final时,Java直接调用它们而不是通过动态的查表的方式。这意味着在需要被动态调用时,它们却是非多态性的函数,Java的这种动态特性使得编译器难以进行进一步的优化。 Eiffel和Object Pascal迎合了这个选项。在它们中,编写子类的程序员必须指定他们所想进行的重定义动作。我们可以从这种做法中得到巨大的好处:对于以后将要阅读这些程序的人及程序的将来维护者来说,可以很容易地找出来被重写的函数。因而选项2最好是在子类中被实现。 Eiffel和Object Pascal都优化了函数调用的方式:因为他们只需要产生那些真正多态的函数的调用分配表的入口项。对于怎样做,我们将会在global analysis这节中讨论。 选项3、 纯虚函数这样的做法迎合了让一个函数成为抽象的,从而子类在实例化时必须为其提供一个实现这样的一个条件。没有重写这些函数的任何子类同样也是抽象类。这个概念没有错,但是请你看一看pure virtual functions这一节,我们将在那节中对于这种术语及语法进行批判讨论。 Java也拥有纯虚方法(同样Eiffel也有),实现方法是为该方法加上deffered标注。 结论: virtual 的主要问题在于,它强迫编写父类的程序员必须要猜测函数在子类中是否有多态性。如果这个需求没有被预见到,或是为了优化、避免动态调用而没有被包含进去的话,那么导致的可能性就是极大的封闭,胜过了开放。在C++的实现中,virtual提高了重写的耦合性,导致了一种容易产生错误的联合。 Virtual 是一种难以掌握的语法,相关的诸如多态、动态绑定、重定义以及重写等概念由于面向于问题域本身,掌握起来就相对容易多了。虚拟函数的这种实现机制要求编译器为其在class中建立起virtual table入口,而global analysis并不是由编译器完成的,所以一切的重担都压在了程序员的肩上了。多态是目的,虚拟机制就是手段。Smalltalk, Objective-C, Java和Eiffel都是使用其他的一种不同的方法来实现多态的。 Virtual是一个例子,展示了C ++在OOP的概念上的混沌不清。程序员必须了解一些底层的概念,甚至要超过了解那些高层次的面向对象的概念。Virtual把优化留给了程序员;其他的方法则是由编译器来优化函数的动态调用,这样做可以将那些不需要被动态调用的分配(即不需要在动态调用表中存在入口)100%地消除掉。对于底层机制,感兴趣的应该是那些理论家及编译器实现者,一般的从业者则没有必要去理解它们,或是通过使用它们来搞清楚高层的概念。在实践中不得不使用它们是一件单调乏味的事情,并且还容易导致出错,这阻止了软件在底层技术及运行机制下(参见并发程序)的更好适应,降低了软件的弹性及可重用性。
对于“潜在陷阱”的“警告”是C++著作的一大特色:BjarnecStroustrup的书中就包含了许多这样的“告诫”,而ScottcMeyers的书则给出了更多.他们这样为C++辩护:只要你知道了需要避免的问题,那么万事无恙. 然而警告并不够,语言本身就应该被设计得更为健壮.购车的人可不会对汽车销售商的“警告和劝诫”买账——你听过谁这样宣传自己的汽车吗?“警告:本车型驾驶途中偶尔会向左猛偏,如果从Amalfi开往Sorrento,它会爆炸……”. 市场炒作(hype)的一大来源是人们常常和“最新技术”闪电恋爱.我们深深地迷恋计算机,因为我们亲眼所见它们是多么强有力.我们特别会对自己使用的第一项技术产品一见钟情.很自然地,我们很爱自己的第一辆车,但是该发生的总会发生——有一天,我们发现应该换新车了,否则就不得不花一大笔钱来维护那辆老古董.当然,不断地更新换代正是发烧一族想要的,但对于只是将车当作一种实用的交通工具的人来说,情况就并非如此了.计算机行业已经为维护超期运行的系统而付出了太大的代价——典型的例子是“千年虫”问题. 人们也趋向于钟情具有较多用户基础的成熟技术,因为这使他们觉得自己属于一个更大的文化群体.这倒未必是坏现象,但当“强势文化”开始欺凌小群体时,当对某种技术的“忠诚”阻碍新技术的发展.窒息创新时,这种“随主流”的现象就必须受到批判了.在科学史上,这样的现象并不罕见:人们竭力保护已经建立的错误观念,只是为了保护某一权力集团的既得利益不受损害.在计算机行业,这样的权力集团更为强有力,在经济上也占有有利的地位.人们也许会将伽利略看作是这种现象的典型受害者,但在当时,对伽利略犯下罪行的那些人,可是被视为最为聪明也是最受尊敬的精英人物. 技术必须不断地被重新评价,这样它们才能进步.我们必须看到并承认现行技术的缺陷,无论我们是多么地为它们所深深陶醉.被观察到的缺陷,毫无疑问,对于某些不断地释放烟雾以遮盖事实真相的人而言是一剂苦药——这样的抵抗是业内常见的“宗教战争”之源.“宗教战争”会毁了我们的专业精神! 市场炒作非常有助于市场商人提升公众对他们产品的注意力.我最近曾听一位时装业营销人士说过,一件白衬衫不过是一件白衬衫,但一件经过市场炒作的白衬衫是你想买的白衬衫.我们不应谴责那些采用市场炒作手段的人,因为想要切入一个已有某种具极高用户忠诚度的产品居统治地位的市场,这是必然的选择.但我们对技术的正确评价不应该受这些市场手段或者产品忠诚度的影响.计算机行业是最具忠诚度的行业之一.作为消费者,我们需要对不实市场宣传造成的假象多加小心. 我们应该以实用主义的目光来看待语言功能,并专注于这些功能如何作用于软件项目的生产效率.对每个语言功能都能以事实论证来支持“好”.“坏”.“容易导致问题”. 人们过于热情地采纳面向对象技术,以至于很多人开始觉得要被这种热情“烤焦了”.我本人的面向对象学习经历始于悉尼大学,当时JancHext教授给我们上关于数据抽象的荣誉课程,在课程中我们学了Simula.我对这个语言印象很深,不少其他人同样如此,包括BjarnecStroustrup和AlancKay.几年后,我开始使用ObjectcPascal来做几个大型项目,其中的一个项目使我结识了C++.于是,我充满激情地买了BjarnecStroustrup的The C++ Programming Language第一版.在阅读过程中我略感不安,因为我这个有着几年经验的面向对象实践者,对于书中的部分概念难以理解.似乎这些我熟悉的概念被某种复杂性掩盖了.在那时,C++还是非常简朴的——与当今的C++比确实如此.最初,C++比之ObjectcPascal并无实际优势可言,那时它不具备多重继承.模板等.我不想脱离实践经验地来评判语言,因为我觉得实践经验应该可以很快澄清一些模糊之处. 一个行业中的专家群体可以更具自我批判精神.医生们似乎很愿意让病人经受他们(指医生)自己不愿接受的治疗过程.许多医疗手段天生具有危险性,而寻找替代手段的研究则进展缓慢.许多医疗手段基于问题发生后的“再修补”,而非关注病人的整体生活方式并着眼于有效的先期预防.所以,毫不奇怪,有那么多人在寻求其他的医疗手段.问题在于,有太多的人不负责任地鼓吹伪科学的“替代手段”,而且其中不乏试图投机获利的骗子.但是,我们也应该看到某些替代手段背后隐藏着真理,也许它们才是正确的解决之道,可惜的是,行业中的专家群体往往本能地拒绝它们. 不幸的是,计算机行业也存在这样的问题.出于安全性考虑,为了维护某些举足轻重的大公司的利益,许多计算机专家都不愿意探索和鼓励替代手段.如果人类要继续进步,我们应该对研究投入更多,而那些实践者们则应该变得更为心胸宽广. 专家们也被各种规则束缚了手脚,而不幸的是,这些规则常常并不正确.例如,如果某一条规则具有欠缺之处,人们常常制定第二条规则来弥补修正,而非从根本上找出原因解决问题.当然,这并不意味着我们不需要监督者来制止行业中投机者哗众取宠.不劳而获的行为. 目前计算机行业中还有许多人用漏洞百出的论据来为现状辩护,这一事实说明了我们还有很长的路要走. "为了增进人类总体的幸福,只关注应用科学是不够的.对人类本身及其命运的关注,永远应该成为所有技术努力的主要兴趣所在.为了让我们思维的创造能为全体人类谋求福利而非带来灾祸,我们还应当关注劳动力组织与产品分配这两个重大而未决的问题.当你们沉醉在图表和方程中时,不要忘了这一点." ——阿尔伯特·爱因斯坦
纯属个人爱好,窃以为几种语言连起来一起用是最好的了,唉……
我觉得,最重要的不是C++烂.而是有很多自以为是的很烂的C++程序员.
我用VB的,平易近人一点,编得开心一点. 例如VB:if ... then.... 连小学生都明白. C: if(..){..};(如果现在的C语言的人死光,只有天知道是什么意思)
其实我个人觉得不一定完全是c++的问题,当然某种程度上由于c++的"易用性"(当你面对一个刚学习程序的人,当你发现你无法给他解释一个简单语句后面可能发生的事情的时候)导致很多程序员缺乏对底层的了解,但这不仅仅是c++造成的吧 :),越来越复杂的系统 @_@,迫使你依赖某些额外的东西。 C++的确有时候很讨厌,比如一大堆的封装,导致你很难理解某代码内部运行机制(某些有意无意的封装),c++带来的问题有点像交流的问题,c++的封装通常非常个人的创造,这也带来一个问题,一个不熟悉你风格或者思路的人通常很难理解。我想看c++代码的人有时候会有一定感触。哈哈,相比较而言,可能c更直接,我要实现什么功能,就直接了当,上,而不用考虑7788的东西。 这一切都是越来越复杂的系统带来的问题。当然c++由于其目的,肯定会被更方面的语言替代,而c,由于其纯粹性(接近底层),估计在很长时间内很坚挺。
重剑无锋 其实我个人觉得不一定完全是c++的问题,当然某种程度上由于c++的"易用性"(当你面对一个刚学习程序的人,当你发现你无法给他解释一个简单语句后面可能发生的事情的时候)导致很多程序员缺乏对底层的了解,但这不仅仅是c++造成的吧 :),越来越复杂的系统 @_@,迫使你依赖某些额外的东西。 C++的确有时候很讨厌,比如一大堆的封装,导致你很难理解某代码内部运行机制(某些有意无意的封装),c++带来的问题有点像交流的问题,c++的封装通常非常个人的创造,这也带来一个问题,一个不熟悉你风格或者思路的人通常很难理解。我想看c++代码的人有时候会有一定感触。哈哈,相比较而言,可能c更直接,我要实现什么功能,就直接了当,上,而不用考虑7788的东西。 这一切都是越来越复杂的系统带来的问题。当然c++由于其目的,肯定会被更方面的语言替代,而c,由于其纯粹性(接近底层),估计在很长时间内很坚挺。
有一个问题请教:如果要楼主设计一个比较大的商业性的应用系统,其中的中心服务器(适应于WIN和Linu,unix)用C++还是用JAVA? 有人说用JAVA,因为它便于移植(可以说不用移植) 有人说用C++,因为它性能好. 还是开发多套版本以用于不同环境?
楼主是搞底层的高手啊.. 我觉得C和C++各有所长. C比C++更适合搞系统级软件,但在搞商业性的应用软件时用C的话是不适合的. 不知道说得对不对,请指点..
哇,楼下的代码贴的好整齐!
to david: 我觉得你这个实现实际上比C++的要复杂,每个函数都多了一个参数,而且你一样需要将结构体的定义放在头文件中。 //------------------------------------------------------------------------------ // c-implement.h //------------------------------------------------------------------------------ enum PURPOSE { NOPURPOSE = 0, INITPURPOSE, SHUTDOWNPURPOSE, FRAMEPURPOSE }; struct STATE { FUNCPTR func; struct _STATE* next; }; struct _STATEMANAGER { STATE* stateparent; }; typedef void (FUNCPTR)(void funcptr, long purpose); typedef struct _STATE STATE; typedef struct _STATEMANAGER STATEMANAGER; STATEMANAGER* SMCreate(); void SMDestroy(STATEMANAGER *); void SMPush(STATEMANAGER* sm, FUNCPTR funcptr, void* dataptr); BOOL SMPop(void* dataptr); void SMPopAll(void* dataptr); BOOL SMProcess(void* data_ptr); //------------------------------------------------------------------------------ // c-implement.c //------------------------------------------------------------------------------ include "c-implement.h" STATEMANAGER* SM_Create() { STATEMANAGER* sm = (STATEMANAGER*)malloc(sizeof(STATEMANAGER)); assert(sm); sm->stateparent = NULL; return sm; } void SM_Destroy(STATEMANAGER * sm) {} void SMPush(STATEMANAGER* sm, FUNCPTR funcptr, void* dataptr) {} BOOL SMPop(STATEMANAGER* sm, void* dataptr) {} void SMPopAll(STATEMANAGER* sm, void* data_ptr) {} BOOL SMProcess(STATEMANAGER* sm, void* dataptr) {}
实在要贴代码的,可以考虑加上 pre 标签。
晕,代码全乱了 :)
To 楼下的: 你这个用C实现代码不会多多少吧。 而且用C实现, 头文件只暴露接口, C++反而暴露了些成员。 //------------------------------------------------------------------------------ // c-implement.h //------------------------------------------------------------------------------ enum PURPOSE { NO_PURPOSE = 0, INIT_PURPOSE, SHUTDOWN_PURPOSE, FRAME_PURPOSE }; typedef void (FUNC_PTR)(void func_ptr, long purpose); typedef struct _STATE STATE; typedef strcut _STATEMANAGER STATEMANAGER; STATEMANAGER* SM_Create(); void SM_Destroy(STATEMANAGER *); void SM_Push(STATEMANAGER* sm, FUNC_PTR func_ptr, void* data_ptr); BOOL SM_Pop(void* data_ptr); void SM_Pop_All(void* data_ptr); BOOL SM_Process(void* data_ptr); //------------------------------------------------------------------------------ // c-implement.c //------------------------------------------------------------------------------ #include "c-implement.h" struct _STATE { FUNC_PTR func; struct _STATE* next; }; struct _STATEMANAGER { STATE * stateparent; }; STATEMANAGER* SM_Create() { STATEMANAGER* sm = (STATEMANAGER*)malloc(sizeof(STATEMANAGER)); assert(sm); sm->stateparent = NULL; return sm; } void SM_Destroy(STATEMANAGER * sm) {} void SM_Push(STATEMANAGER* sm, FUNC_PTR func_ptr, void* data_ptr) {} BOOL SM_Pop(void* data_ptr) {} void SM_Pop_All(void* data_ptr) {} BOOL SM_Process(void* data_ptr) {}
以后不要在这里贴代码了。我就是用 MT 随便架了个 blog ,对贴代码支持的不好,也不想折腾。 另外大篇的代码贴上来,一般人都没心思看。 至于改写,没啥必要。描述好问题,用相应的语言设计并编码就成了。
楼下这段代码我好像在一本《DirectX角色扮演游戏》里见过类似的
请教云风大大,这段代码用C怎么改写? enum PURPOSE { NO_PURPOSE = 0, INIT_PURPOSE, SHUTDOWN_PURPOSE, FRAME_PURPOSE }; typedef void (*FUNC_PTR)(void* func_ptr, long purpose); //========================================================================== // Defines for state manager. //========================================================================== class STATE_MANAGER { private: struct STATE { FUNC_PTR func; STATE* next; STATE() { func = NULL; next = NULL; } ~STATE() { delete next; next = NULL; } }; protected: STATE* _state_parent; public: STATE_MANAGER(); ~STATE_MANAGER(); void Push(FUNC_PTR func_ptr, void* data_ptr = NULL); BOOL Pop(void* data_ptr = NULL); void Pop_All(void* data_ptr = NULL); BOOL Process(void* data_ptr = NULL); }; //----------------------------------------------------------------------------- // Constructor, initialize state pointer which pointer to parent state. //----------------------------------------------------------------------------- STATE_MANAGER::STATE_MANAGER() { _state_parent = NULL; } //----------------------------------------------------------------------------- // Destructor, pop off all functions. //----------------------------------------------------------------------------- STATE_MANAGER::~STATE_MANAGER() { Pop_All(); } //----------------------------------------------------------------------------- // Push a function on to the stack. //----------------------------------------------------------------------------- void STATE_MANAGER::Push(FUNC_PTR func, void *data_ptr) { // don't push a NULL value if(func != NULL) { // allocate a new state and push it on stack STATE* state_ptr = new STATE(); state_ptr->func = func; state_ptr->next = _state_parent; _state_parent = state_ptr; // call state with init purpose state_ptr->func(data_ptr, INIT_PURPOSE); } } //----------------------------------------------------------------------------- // Pop a functoin off the stack. //----------------------------------------------------------------------------- BOOL STATE_MANAGER::Pop(void *data_ptr) { STATE* state_ptr; // remove the head of stack (if any) if((state_ptr = _state_parent) != NULL) { // first call with shutdown purpose _state_parent->func(data_ptr, SHUTDOWN_PURPOSE); _state_parent = state_ptr->next; state_ptr->next = NULL; delete state_ptr; } // return TRUE if more states exist, FALSE otherwise. return (_state_parent != NULL); } //----------------------------------------------------------------------------- // Pop all functions off the stack. //----------------------------------------------------------------------------- void STATE_MANAGER::Pop_All(void *data_ptr) { while(Pop(data_ptr) == TRUE) ; } //----------------------------------------------------------------------------- // Process top-most function. //----------------------------------------------------------------------------- BOOL STATE_MANAGER::Process(void* data_ptr) { // return an error if no more states if(_state_parent == NULL) return FALSE; // procress the top-most state _state_parent->func(data_ptr, FRAME_PURPOSE); return TRUE; }
to 11: 那么我问的就是用C实现几种类似但又不同的cache(IPv4 cache, IPv6 cache, URL cache, domain cache等等) 如何避免重复代码的问题啊。还请指教。没必要把Python扯进来吧。 至于内存,说成1万台和2万台电脑的区别确实不太合适,不过如果我们用的是廉价的PC,不用专用设备的话,每台电脑能插的内存数目终究是有限的,呵呵。
那个路人甲,说的就是git,而不是linux内核 你去看下原文就知道说的就是git
to welsy 我说的是python实现逻辑部分,你那个ip cache显然应该用c实现,就和linus实现git的分布式存储不用数据库或者其他语言自带的抽象,他自己实现一个一样,, 不会多占据什么资源。而且也不该是1万台和2万台的差别,而是内存有这个差别,这是两回事
to 那位11同学, 首先,消除重复代码对性能没影响。但对代码的可读性和可维护性有影响。我想,大家都认同,基本没有重复的代码才是好代码吧。 其次,不用Python的理由很多,其中一个理由是Python是一种dynamically typed language,它的实现是每个value object都会有一个tag用来表示它的类型,这个tag到底占多少空间我没研究过(可能是implementation/machine dependent的),不过至少一个byte,如果考虑到alignment的问题,在32位的机器上多半是占32bit也就是4个byte。这意味同样实现存IP address的cache,用C实现的话一个cache entry只占32 bit也就是4个byte(存IPv4的address够了, IPv6多一些),而用Python实现的话同样用一个整数来表示IP address,大概就要8个byte了,因为需要额外空间存这个value的type tag。也就是说同样数目的内存,用Python实现的话cache里面存的东西要少一半。如果每台服务器的内存是固定的,那么假设果我们本来用1万台服务器就能满足需要,改用Python实现后就需要2万台了,这增加的不仅仅是硬件成本还有电费和管理成本。做这种以service方式部署的产品是不能不考虑运营成本的。
偶以为,之所以有这个问题,潜台词应该是:Linus老大用C写出了Linux核,而不是什么Git。说C++好的,去用C++搞一个Linux++,估计就不会这么底气不足了。 反过来,是不是说一定要写出了某个OS、kernel才能算得上好语言呢? 语言都是用来抽象的。
补充一下,linus不用数据库而自己写,就是linus作的工作用c没用c++的原因所在,那个走来要用c++的人根本没看到问题所在,c++要完成linus作的那部分工作无用武之地。那部分不需要c++的抽象,c++的抽象可以发挥作用的地方用python代替了
那个wesley,你的消除重复代码真的对性能很有影响? 我觉得这种算法的东西,用python来实现和用c++实现效率基本没什么区别,甚至可能实现的更好,因为python有很多非常高级的抽象和优化在这些算法上,你用c++不一定能设计的那么巧妙,而linus所作的更多的其实是在硬件I/O层上的模型对象设计,换句话说,他那个git其实是linus自己实现了个数据库,而逻辑部分用python实现的。 你那个c++用模板解决重复代码我想如果代码足够大,那么用c抽象一个层出来绝对还是值得的。如果代码量很小,其实无所谓,并不会有什么维护难度 linus用c而不用现成的数据库完成这些个对象的merge,tag什么的,我想他不想用数据库那么大的对象,资源限制了。git可能有几百万个文件,补丁树什么的,如果用数据库,要用的资源就是海量的,而大多功能其实用数据库那臃肿的模型没必要。linus就是用自己来实现这些需要的对象的内存化操作管理,而且需要的对象可以随时定制和添加修改,。 用c++就是大炮打蚊子,深入不到那个层次,用C++也用不到抽象那个层次,换句话说你还是用的c++的c部分,真要抽象的地方,git用python去作了,python对c是天然融合的,可以做到更可读更简介,更高效,无论是逻辑还是硬件,你用C++都做不到这个层次
前面的讨论完全可以编个现代寓言留给子孙后代读了。寓言的名字就叫《瞎子摸动物--新编瞎子摸象》吧。 从前啊,有这样一群瞎子,他们没见过动物,有一天,为了搞清动物是什么,他们要去摸摸动物。他们先去摸大象,在把象腿摸过一遍之后,不屑一顾地说:“原来大象就是根柱子。”然后,他们再去摸刺猬,在碰过刺猬的刺之后,自以为是地说:“原来刺猬就是根针。”接着,他们开始比较起刺猬和大象来。一个瞎子站起来说:“有了针状刺猬,我对柱子状大象感到很恶心,因为针比柱子更锐利,刺猬一定会杀死大象。我预言,大象会远去,刺猬会回归,并将以其无比的锐利统治世界!什么?不对?你们好好想想吧,也许你们还没思考到我这一层。”底下七嘴八舌有几个瞎子在附和,甚至开始欢呼他们找到了真理。小朋友们,谁能告诉我这个寓言故事说明了什么道理呀? 刺猬当然不是针,大象也不是柱子,刺猬更没有统治世界。千百年过去了,刺猬、大象和其他万物一起生活在共同的世界里,当然也包括那些瞎子。希望我们的子孙后代每个人都有机会受到这则寓言的教育,把思维瞎子的数量降到最低。
程序员们可怜到了以讨论两门语言的优劣来找乐子了么??有这个时间,哄哄刚出生的baby多好!
在我看来,所谓设计,就是创造概念的过程,如何实现,就是Linus所谓的BS。设计没有对错,只有好坏。问题在于,人的智力有限,再牛的人,也要依赖更基础的抽象工作。牛人可以同时顾及的东西更多一些而已。但正如过往的研究指出的,人同时能够track的维度实在有限,不过就是5~7个。OK,就算Linus是天才,但我不觉得他会比Andrew Koenig之辈高出一个数量级。而从Koenig的文章中看出,每每代码要track 3个以上的维度时,就要说怀疑其中是不是完全正确了。 我宁愿相信这次不过Linus受人刺激之下的大嘴巴。从过去的历史来看,Linus也确实嘴巴比较大。 在从技术上来说,说Linus是软件工程方面的天才更让人信服一些。De Raadt讽刺linux的话也不是空穴来风。要是有人说Linus是操作系统大师,我宁愿相信Rob Pike的话,现在的操作系统不思进取。
没人提Lua啊?个人觉得C+Lua非常好,C实现物理层,Lua实现逻辑层。
把c 和 c++放到我面前让我选,我会选c++,一定要我用c,也没有问题,但我会找一些现成的库来用,比如bstring之类的。
设计第一,语言第二。 C/C++没什么需要比较的。 太重视语言,人就抽象了。 多做点实事,比谈理论更好。
阿弥陀佛 vs 哈里路亚
to Wesley, 最近我们新项目内部测试,忙晕了。 我想了一下,你的这个问题不太好回答。我用 C 做设计时间不太久,经验积累的没有当初用 C++ 时的多。 先说几句问题之外的话,我的感觉是 C 的解决方案往往是直指问题的。不主张再未了解问题全部之前做半拉子事。也就是我们在 C++ 做设计时常用的那种“抽象”。C++ 做设计时,很容易因为不了解最终要做什么,就先抽象出来把其他的部分完成。 抽象本身也没有错,错的是抽象层次过多。什么叫过多?超过三层就是多了。我认为抽象层次过深造成的危害是略大于重复代码的危害的。(当然两着都是很严重的危害,都需要避免) 话说回来,这个问题本身在你看来不是设计问题,是实现问题。那我们再来考虑这个实际问题。 我对具体问题还不是很了解,也不可能做太多具体分析,只能给出我在我们项目中遇到的类似问题的解决方案。强调一下,我的一个观点是:具体问题即使有细微的不同,解决方案可能差别很大。企图找到通用方案很可能陷入过度设计的泥沼。 我们的系统首先是按模块划分的,这个模块的具体实现可以是系统提供的 so ,也可以是自实现的二进制数据块。在这里我们并不用类似 C++ 的继承手段来实现模板方法。而是用模块 id 来做耦合。 类似 cache基类 的东西会是一个独立模块(对整个系统来说以二进制形式给出,而不是源文件),不同的策略以分离的不同的独立模块存在。 cache 基类需要不同的策略的时候,加载不同的策略模块。它可以通过 id 或 string 从配置里完成耦合。这个过程可以是初始化时做,也可以是运行时做。 我提到的这个设计方案,用在我们的资源管理器上。曾经在 blog 上写过: http://blog.codingnow.com/2007/05/mutilthread_preload.html 。它可以完成从不同介质(内存,本地文件系统,打包文件系统,网络)加载资源;也可以采取单线程和多线程预测加载多种方案。 不过上次没写具体设计。实际上,我提供了一个很简洁的函数接口给最终程序员用,再提供了一套独立接口给扩展策略的程序员用。两者分离可以使每个不同职责的人可以看到相对干净的接口。
晕,如果说设计只有抽象,那么对设计的理解也太过狭隘。
Linus说话自相矛盾。 诚然,除了设计,其他的都是BS.问题在于,Linus在设计什么?难道不是在设计抽象?不依赖抽象进行设计?文件不是抽象?字符串不是抽象?又要强调设计,又要否定抽象,我不知道想表达些什么。 我唯一看出来的就是,不过就是说别人的抽象不如他的好而已。
to Atry: 张口教训人之前先搞清楚别人的问题好不好。我没有问接口问题,没有问设计问题(我说了我设计时只考虑整体构架,考虑选择用何种数据结构和算法,根本不考虑什么语言的)。我问的是实现问题,在我说的那种情况下用C实现如何避免重复代码的问题。 在这里模块就是cache component。别扯什么模块化,也别瞎猜测头文件里放了啥。我前面说得很清楚,这个模块我当时还是决定用C实现的,如果你好奇的话,可以告诉你,头文件里只暴露了几个函数作为interface。没有暴露任何数据结构,而是把结构指针cast成不透明的void* handle传递出来的。大致这个样子(简化过的,以利于说明问题。) void* initialize_cache(int memory_usage); void destroy_cache(void* cache_handle); int cache_lookup(void* cache_handle, char* url, url_rating* rating); cache_update(void* cache_handle, char* url, url_rating* rating); 当然,如果用C++写的话这个接口中的void* cache_handle就相当于this指针了。接口大致上是一个cache类,然后提供update/lookup两个方法。(实际的实现会有一些别的方法,比如持久化什么的,不过这里update/lookup足以说明问题。) 我的问题是,因为`cache_lookup()`和`cache_update()`的内部实现其实是要分别查询好几个cache(比如先后调用`domain_cache_lookup()`, `url_cache_lookup()`等等), 哪个cache命中就返回哪个结果,那么用C如果不用宏的话如何避免让`domain_cache_lookup()`和`url_cache_lookup()`有大段重复代码。这2个函数操作的是类似但不同的`domain_cache`和`url_cache`的数据结构(细节不用我说了吧)。 至于为什么要用不同的结构,前面已经说了,这个应用对性能和内存利用率的要求很高。事实上可以告诉你这个`cache component`是一个web reputation service的一部分,这个web reputation service需要储存大概几十亿或者更多的URL的分类和声誉信息,部署在多个数据中心,为Cisco在全球销售的多款路由器产品提供backend的URL/IP的分类和声誉服务。 我只是觉得用C++的话在这种情况下很容易避免重复代码。用模板可以让操纵不同缓存数据结构的代码只写一遍,让编译器来做copy-paste的工作。用继承也可以实现类似的目的。用template method可以把不同cache的lookup/update逻辑中公共的部分只写一遍,不同的部分分开。但用C怎么办呢,有好的做法吗,我想知道你们推荐的做法。仅此而已。
与楼主工作在同一领域,一直觉得楼主比较牛,看了这篇文章才发现楼主还在讨论这个问题,失望。。。
我算不上精通C++,但也是周围的人中最熟的. 觉得C++ 设计过度了. 现在尽量只用C++中最基本的功能. 这些功能,用C是足够了. 老实说,在这里为C++鼓吹的人,对C++的了解不一定有多深.
用Java吧。
我用脚趾头猜测了一下,你写的代码在头文件用 C++ 暴露了很多不该暴露的东西,然后你开始抱怨 C 没那么容易暴露这些东西。
蠢人,蠢人。你知不知道,C++的常规继承和模板对于模块化只有负面影响,这两个特性只对于减少代码有好处,对于设计是有害的。 我猜你写代码的时候,根本不知道什么叫接口。 说句讨打的话,我鄙视设计模式。主要还不是鄙视设计模式本身,而是鄙视那些愚蠢的程序员用愚蠢的方式使用设计模式。
无意比较C和C++的优劣,只想指出一点,抽象未必是为了复用。更多时候仅仅是为了消除重复代码,在不损失效率的前提下提高代码的可维护性。 举个例子。我曾经做过一个性能和内存利用率要求都很高的缓存组件。这个缓存组件可以存放URL(为了节省内存,只存放URL的fingerprint,比如MD5,一个128bit的整数),以及domain name(这个要求存完整domain name),以及URL + IP address一起缓存(要多存一个32bit或者128bit的整数,取决于是IPv4还是IPv6)。 做设计的时候,我根本没有考虑用C还是C++实现的问题。我只是针对需要储存的不同类型元素,选择了几种数据结构(几种自平衡树,几种不同的internal/external chaining hash table,还有几种采用不同空间压缩策略的trie结构)做了一些分析和benchmark。其他的构架上的design decision也都是和语言不相关的。 实现的时候,为了避免浪费内存,看来只能把这3类情况分开处理,做了3个不同的缓存实现。但是,其实这3个实现很多代码和逻辑都一样的。当然cache entry的结构定义、类型,以及查找/更新cache entry的代码中会有一些细微的不同。 如果用C++的话,可以让3个cache都继承自同一个cache基类,把相同的代码放到基类的实现里面去,也可以把cache_lookup和cache_update所操作的结构定义成template,然后把那些不同的操作特化一下。 但C里面就没有什么好用的抽象机制来消除重复代码了。用宏?可读性和可维护性比较差。copy-paste然后做些细微改变?可维护性很差。 从设计的层面看,有个叫template-method的pattern可以解决这个问题。但要实现的时候就会发现这个pattern需要语言提供继承语义,否则很难实现。当然,我们可以用C来模拟一个继承的实现。但既然如此为何不用C++呢。 其实,这个缓存组件,我后来还是选择了用C写。不过我想这类“为了消除重复代码而需要抽象”的情形应该是编程时会经常遇到的。这里的抽象和复用无关。我不太清楚云风或者其他喜欢用C的朋友都是如何解决的呢?
关于编程语言的争论就跟关于宗教信仰的争论一样没完没了,并且经常是非理性的,即使偶而出现比较理性的论点,也往往难被其他人接受。 其实嘛,人的胃口和喜好是如此的不同,即使再糟糕的语言也还是会被一部分人喜欢,再好的语言也可能会令另一部分人讨厌。况且语言也确实存在不同的最佳应用领域,抛开应用领域比较语言的优劣肯定会衍生为没完没了没有结论的争论。 在一个项目上,云风从C++转到C似乎不能作为C更优于C++的佐证。我想在用C重构C++程序时,在设计方面某些地方也许还得归功于前面使用过C++。我也用C重构过一个C++程序,结果也很令我满意,感觉用C重构后的比原先C++写的好。但我觉得先用C++写对后面重构帮助很大。要是我一开始就用C写,肯定会写得一塌糊涂;-)。不知道云风是否也觉得先前用C++开发的游戏引擎对后面用C重写有比较大的帮助。 另外,我觉得,C似乎对人编程的功底要求比较高,功底不够,写出来的程序肯定既难看又难维护。C++在这方面要好一些。如果一人C功底好又不怕多写代码的麻烦,选C是情有可原的,因为C给他们的束缚更少(就是云风这类人;-))。如果要快速开发一个大型应用程序且能满足效率要求,C++应该是比C和动态脚本语言更好的选择。
C++社区努力实现了STL,BOOST之类的库,很大程度上就是为了为了代码复用,尽管你认为这些努力有时候看起来不是那么的成功. 问题是,对比之下的C, 这方面是更加的失败. 我认为之所以C++被Linux这样的内核开发人员所唾弃,有一个重要原因是,他们如果用C++,等于是被强迫适应另一帮行家所实现的产品,而身份内核开发人员习惯控制一切,了解一切,怎么可能接受一个不清楚的std::string实现!! C和C++尽管他们有很大的重合,但来到底层开发这样一个特殊的领域,还是有很大分野的. 而且在底层开发讲求的是效率,稳定性等诉求,C++能给予的帮助真的并不多.在这个等级对C/C++进行取舍是有其特殊背景的,并不能把结论随意推广. 你可以证明C实现了伟大的OS,但C++同样也可以证明它实现了规模更大的Vista和无数的软件. 他们争吵的这个背景相当特殊,对C来说差不多是最后的重要领地了. 补充一句,C++最大的问题是它太容易被不恰当地使用了.
不少人为这个争论找台阶下,说 C 和 C++ 各有适用的领域。而我不这样看。 这几句话本来不想说出来,因为这太不厚道了。对这个无聊的话题,保持沉默才是最好的态度。 刚才看见下面 fastzhao 说的 symbian os 的例子,感觉真是太有趣了。本来我想拿这个例子来败坏 C++ 名声的 :D 看来什么样例从不同的人放在不同的角度来说,就有不同的效果。因为这件事的有趣,忍不住还是回复了。 我对 Linus 的话产生共鸣,正是因为他说出了我心底的话。Linus 大大在整个争吵过程中,从来没放什么适用领域这种话。他只是说,你若想用 C++ 做的版本控制系统,那就用去。世界上有这种选择,没必要来用我的 Git 。 操作系统也可以用 C++ 写,symbian os 就是一例。我 2000 年接触这个东西,那个时候叫 epoc 。曾经有两个月就是靠这玩意骗钱吃饭。 后来用上了仰慕已久的 symbian 手机,那叫一个痛苦: (见 http://blog.codingnow.com/2006/07/eoeueoiaee.html )。还是现在换成了 palm 才觉得玩手机也很有乐趣。 游戏引擎也是这么回事,其实用 C++ 做这个工作的人可能更多。并非适用面决定了该用什么语言开发。 我始终认为 C 和 C++ 适用领域是高度重合的。 谈到效率,论用程序完成一件事情的理论效率极限,C 是比不上 C++ 的。原因很简单:C 是 C++ 的一个子集,多出来的那些语言特性理论上可以提升代码效率。 追根到底,软件的质量(可维护性,可扩展性,效率)这些是程序员本身的素质决定的,而不是他们用的语言。 强迫一个热爱 C++ 的程序员换 C 去做软件,软件质量不可能上升。 但是,当他发自内心的回到 C 后,情况就不一样了。 为什么会发自内心的做这件事情?如果能写清楚并用文字说服其他 C++ 程序员。那么早有牛人写出来了,轮不到我这种小字辈。
其实语言之争没有任何意义,语言只是个信仰的问题。但是我还是要为c++说几句公道话。 普遍都认为c++不适合做系统开发,那么请问symbian os是用什么开发的。 nokia和索爱的智能手机大部分都采用symbian os。 symbian os就是将c++用于系统开发最好的例子。其中用的什么瘦模板,分段构造c中有吗?所以说c++不是你想像的那么可怕和阴险。而是看你怎么用它了,c++的设计思想就是提供好多特性,让你选择。 而c呢? 你恐怕没得选择吧!
前面那个叫着用Ada的,牛叉是牛叉,就是在国内难找工作.
一帮疯子 C和C++和Java都有自己适合的用武之地,谁也抢不了谁的地盘. 我现在作的项目就用C++,改用C?让我去死吧. 改用Java?死得更快. 只要想清楚这一点,利用语言,而不是被语言俘虏,那也就没有什么好争得了. Linux就其项目而言说C++不合适也算可以理解,如果推而广之来骂C++,那其心真可诛也.
的确是信仰问题。
从这个看起,把孟岩和刘江的blog看过后,竟然又燃起了我从前对C++技术学习的热情,我爱C但更爱C++。
用vc的话 ms为C提供了__inline扩展
都用ADA2005吧,比C、C++都牛叉!
为什么会有这么多语言?每种语言自然有它擅长的一面,世界上没有十全十美的东西,要不然也不会冒出这么多的。我们还是要用最简单的方法去实现最复杂的事情,当然也不是要去学会所有工具,只要精通一样就可以了,谁有三头六臂呢?
本是同根生, 相煎何太急. C/C++本來就是用來開發系統級應用的, 有何必強分彼此呢? 在程序開發中, 最重要的根本就不是語言,而是設計(在這點上面, Linus說得是對的). 之後的問題: 效率, 重用, 擴展等等問題都取決於你的設計和軟件的要求. 這個並不存在絕對的標準. 說C++開發不好的兄弟們, 請考慮考慮, 如果你用C改寫同樣的軟件, 你可以保證寫出來的軟件質量就一定好過用C++寫的? c/C++當然存在個自適用範圍的情況, 一般認為C比較適合OS內核和遷入式的開發. 而C++一般不太適合在這兩種環境下面. 這個原因是很多的, 其中很重要的一個原因是由於C++的OO特性會產生不必要的負載(其實, 如果軟件設計得當的話,加上一個合適的編譯器, 這個並不是很大的問題. 在這裡,就假定C++不適合這些領域). 但是做遊戲領域呢? 圖形介面領域呢? C++就非常合適(而C在這裡就顯得麻煩了). 兩種語言都有長處和不足. C++由於提供了太多的內容(OO/模板/泛型...), 導致了大家有太多的選擇, 結果在軟件設計的地方錯用了C++的特性, 導致了軟件的各種問題(效率差, 不可擴展等等). 這個是設計的問題, 而不是語言錯誤. 不要把自己的設計缺陷推到C++上面, 然後反過來指責C++. 老實說, 這樣的設計水平, 用任何語言都做不出來好的軟件. 至於Linus說的C++是垃圾之類的話, 不用去當真. 他開發出了Linux, 是很好. 但不表示他說的話就是真理. 我相信那只是爭論時候的激憤之言(而且有特定的環境, 某位老兄批評了一把他寫的git軟件, 有點小器). Linus是人, 不是神. 用不著頂禮膜拜. 軟件質量取決於設計而不是開發語言.
Linus自己发起的开源项目,有人上来说要改,他肯定不爽。 自己写的代码,别人说要改改,肯定是稍微有点抵触的,人之常情可以理解。 不过他这次那么激动,一上来就bulls**t大动肝火,还是稍微有失“大师”风范。 盛怒之下说出来的话,大家大可不必太当回事。他自己爱用C,用就是了。我的项目,还是C++。并且以后很长一段时间估计我,还有一批和我一样的人,都要靠C++吃饭。 大师帽子不好戴啊。
我看了一点不觉得奇怪. linus并不是说c++不好. 他意思是c++的一些特性导致其使用者容易写出糟糕的代码. 他希望linux团队是优秀的程序员,因此他极端地把c++程序员拒绝在外而达到拒绝一部分糟糕代码出现的可能.仅此而已.我觉得c++很好.但是如果没有一定深度的认识,(语言,库,编译器平台等)确实不容易写出好代码. 就像java的hibernate一样,有些人骂它,有些人赞它;如果使用不当可能弊大于利.所以这些东西是把双刃剑. 面向对象垃圾吗?可是现在java满天飞,动态语言满天飞. 再说了,C程序员都是优秀的程序员吗?C代码都一定比C++代码优秀?所以其实关键是与这代码的作者有关. 我其实能理解linus 作为一个写代码的是有时候心中会有不快.是人总是有情绪失控的时候,是人总是需要发泄的地方,linus 只是选择了向公众发泄他的不满.
其实语言之争没有多大的意思。每种语言都有它自己擅长的地方,关键是要针对特定的问题,去选择最适合解决这个问题的语言,用什么语言实现能做到简单,就应该用什么语言。c有它适用的场合,c++也有它适用的场合。
程序总能在现实生活中找到一些类似的对应.有的人吃饭用筷子,有的人用叉子,要说哪个更好,恐怕是吃面条的时候前者优越,吃西餐的时候后者方便.若说到前后的历史关系,筷子的前身也许是树枝,叉子的前身恐怕也是一些原始的工具.
程序总能在现实生活中找到一些类似的对应.有的人吃饭用筷子,有的人用叉子,要说哪个更好,恐怕是吃面条的时候前者优越,吃西餐的时候后者方便.若说到前后的历史关系,筷子的前身也许是树枝,叉子的前身恐怕也是一些原始的工具.
C 是 C++ 的一个子集(从 C99 开始已经不是了) 这句搞笑...C99增加那点儿玩意儿 很多是C++用了好些年的了...
C和C++到底有什么区别?
一群吃饱了没事干的人。 语言只是我吃饭的工具。 就象刀叉和筷子,你们能说哪个好吗?
C++开发应用系统最困难的两个地方是内存和线程问题,其中内存问题是C++语言本身的问题,线程问题则是应用问题(问题域),所有的语言都要面对的。 强烈建议C++增加GC支持,对通过new创建的对象通过GC管理。同时栈上的对象维持现状。 其他问题本人认为是个人应用水平的问题,自己提高就是了。
我隐隐担心的是,这么庞大的代码,它的设计不可能是永远正确的。 ---看了楼主的话,觉得你的见识实在不怎么样。 简单的讲,拿一门结构化的语言和一门发展的很宽泛的语言来相比,就是有问题的。Linus这今年越来越像是疯狗了
如果在商店买了质量不好的东西,你却不对商店表达不满或提出投诉。那么,不久以后,你将会找不到令你满意的商店了。不是这样吗? 无论在生活中还是在工作中,适当而合理的表达自己对某些事物的不满是一种权利,更是一种义务。在沉闷的环境下,更希望听到不同的声音,“呐喊”就是这个意思,需要的是勇气。
去商店买东西 有的不一定是你想要的 如果需要你可以凑合使用 如果不想凑合 要那就换一家店在看看把 没必要诅咒商店 毕竟 商店只是提供一种选择
c语言解决问题很直接了当。 c++提供了一个尽可能完美的解决方案,但是这就带来了更大的复杂性。 简单就是美。这也适用于软件设计,因此c能设计出稳定持久的库。 但现实往往是复杂的,尤其在发展的初期,我们需要的是c++这种快速完成作品,并支持快速修改的的语言。虽然这个所谓的设计存在很多不和谐之处,但现实就是这样,不和谐的东西总比和谐的少。
to 張杰 楼主,你知道世界上已经有了几亿行C++代码?有多少重要的系统,比如.net系统,多少国防,民航,核电站控制系统,是用C++写的? ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 請你給出有多少用C++ 寫的系統吧. 順便給出它們在該系統中佔有的位置, 是核心系統, 還是外圍系統.
有些人误会我的意思了,我纯粹是抱着学习的态度在这里发问的。我相对于C++来说更偏好C一些,要么我也不会一直想找出一种C的替代办法。 有可能我真的误会你的意思了,那你问的就跟这个贴子主题不符合了,你能把你的函数完全的贴出来吗?而不是描述来描述去。或者你换个地方提问吧。给个链接我们就可以了。
文章写的很好,怎么不去http://blog.csai.cn,里面有很多技术人员都在里面开博
C语言虽然简单,但在开发库的时候绝对是首选。现在很多基础的库都是C语言。 当然,自己现在为了图方便选C++。
有些人误会我的意思了,我纯粹是抱着学习的态度在这里发问的。我相对于C++来说更偏好C一些,要么我也不会一直想找出一种C的替代办法。
最近关于C/C++的争论很火,起因是linus的一篇批评C++的mail. 很多"大家"已经发文说了很多了, 我在这里也不想陷入语言之争,只想提醒大家一件往事. 很久很久以前,世界还是以欧洲为中心的时候,法语被认为是最优美的语言,词汇和发音有很多变化.听人说法语就像在听歌,韵律优美.开国际会议的时候,大家都说的是法语.(就像当年的C) 但是,这个时候,有人觉得法语太复杂了,不便于,人们学习,所以,特地发明了一种无根的语言------世界语.这种语言和法语有点像. 世界语出来后,一度被认为是国家无关的语言,受到部分第三世界国家的文人的推崇. 但是,一个国家无关的语言真的是合适的世界语么??? 现在的世界语是英语,处于第二语言地位的是法语.世界语早已不见踪影,只在google的搜索语言支持里有一项而已. C++的尴尬地位就和世界语一样.平台无关?ISO标准? 这些东西最终害死C++. 回到语言上来说,英语这种发音没有法语优美,拼写比世界语复杂的二流语言之所成为世界性语言,是因为美国. 美国集中了世界最聪明的科学家和工程师,当他们都使用用英语的时候,英语就成了"世界语"! 在计算机语言界,只有大厂商支持的语言才能走的远,Java有IBM和半死不活的SUN,C#/C++CLI有微软, C++有什么?? ISO???? ISO做软件么?? ISO甚至不写代码. 让一个不写代码的机构制定C++标准?和让一个哑巴来制定语言有什么区别??? 这让C++和当年世界语的处境很相近. C++社群愚蠢的拒绝了微软的橄榄枝C++/CLI,导致最后一个可能支持C++的大厂商心灰意冷. 这就是C++的灭亡之路. 不过,好消息,是C++程序员的工资会和当年COBOL程序员一样飞涨~ :)
又是语言之争... ASM、C、C++、Python、Java...每一种语言都有它存在的理由,都有它擅长和不擅长的地方,选择适合你的就可以了。博主用C代替C++重写了游戏引擎,这能说明什么,说明C比C++好吗?最多只能说明在开发游戏引擎这一块C比C++更合适。
糊涂了,真的糊涂了,不知道该学什么好了
多谢大家指教! 不过我的意思是“那句话”在一个大于一层的内循环里,所以不能根据参数来判断,也不能接受函数指针的开销(还是要调用函数嘛)。 //============ OS对效率要求高吧,quake2在当时代算效率高吧,C不也照样完成了,你这个要求建立在一种“C++无所不能”的基础上,真不知道你这种优越感是从哪里来的。C++总有人喜欢拿qsort说事,我不知道他们有没有碰到过C无法解决的效率问题。倒是C++中的那些什么string,vector的效率倒是比较搞笑,既然自己的库效率也不算高,为什么还要跟人家来比较。既然自己不是无所不能(知道你不信上帝),为什么不老实承认。
糊涂了,设计和抽象是什么关系呢,我理解某个设计必然是某种抽象的实现,怎么会对立?请大家解惑。
多谢大家指教! 不过我的意思是“那句话”在一个大于一层的内循环里,所以不能根据参数来判断,也不能接受函数指针的开销(还是要调用函数嘛)。 Rome的回答应该更接近我的要求。不过是否有更好的答案呢? //========================== 我想你所说的无非就是有些事情C++能做到,而C不能做到。 1、这种情况出现的可能性非常少,在几个项目中碰到一个已经很难得了,在这种情况下用宏用汇编我想都是可以完成你所要完成的事情。 2、我们讨论的不是你这种小概率事件,是大多数情况下要做的事情,小概率事件可以通过特殊手段来做。
今年2007年了, 不知道20年后美国人登上火星的时候会用什么语言控制他的飞船推进器, 人类总是向前发展的, 在出现争议的时候, 我们应该如何辨别是非呢? 那么请尊重数学原理吧, 感觉归感觉, 猜测归猜测, 只有数学上证明或者证伪的东西, 才有说服力, 我更希望看到的是证明的过程, 而不是武断的结论。
有没有war3玩家,有机会什么时候挑一把
没办法,看见语言之争,感到心里不爽。或许自己水平太低,不够发表评论的资格,虽然这样也要把自己的真实想法大声地说出来。 个人认为,语言无好坏区别,只有在特定使用场景和环境当中,才能显示出来。就拿作者的游戏引擎来说,或许对于游戏引擎的开发,要求更高的可能就是性能和响应处理速度。当然c更接近low level,使用得当肯定比c++快的,除非是初略的代码设计,即使再好的语言也会被用烂,当我们在评论某种语言好坏的时候,应该是否先从自身讲起。linus大牛,人家编写linux内核,大家是否想过他最在乎的是什么?
一群跟风的, 当linus说某某的屁是香的,也点头附和. 当linus说鸡肋非常好吃,个个都说好吃. CSDN的孟*也是此辈. 某地主患了花柳病,好几个郎中看不好,给一个秀才医好了.众郎中齐来问,你医好他,你读的是什么经啊.秀才不语. 甲郎中:我读的是黄帝内经. 乙郎中:我读的是医经, 丙郎中:我读的是"本草纲目", ...... 秀才怒道: 我读的是卵经. 众人无语. 剑有剑道,刀有刀法. 呵呵.如此简单下结论是不厚道啊.
我反对信任程序员这一原则, 我认为程序员自己都不应当相信自己. 对于我来说, 开发工具越高级越好, C++比C好, JAVA,C#比C++好, 动态语言比JAVA C#更好. LINUS认为C和C++之间于ASM和C之间不可比, 我认为这本质就是一回事! 弃C++用C与弃C用ASM本质是一样的. 但是我理解LINUS的立场, 如果某个家伙建议我用ASM开发某个项目, 我会断然拒绝, 并提议改用PYTHON什么的语言来反击, 但是如果那个家伙自己已经拿ASM实现了一个项目, 我除了称他为大侠还可以说什么呢? 没有任何批评他的理由, 斗胆去批评用ASM写代码的人本身就没有程序员的思维模式.
多谢大家指教! 不过我的意思是“那句话”在一个大于一层的内循环里,所以不能根据参数来判断,也不能接受函数指针的开销(还是要调用函数嘛)。 Rome的回答应该更接近我的要求。不过是否有更好的答案呢?
相对于代码的可重用性,我更加看重代码的可维护性和可扩展性。好的代码都是开放的,可维护,可扩展的。尽量保持扁平的层次结构,尽量使用基本的数据类型,尽量减少自定义的数据类型,使整个系统的设计更加容易被人所理解。 真正好的设计,都是开放的,简洁的,易于理解的。毕竟维护的最基本的条件,就是先要理解现有的代码。 而所谓的可重用性和抽象,都给以上目标的实现,带来了极大的障碍。因此需要根据实际需求,在各个方面取得平衡。否则,一切关于语言的争论都是毫无意义的。 一个系统,不管你设计得多么抽象,多么可重用,他总有适用的范围。而为了达到重用和抽象,所使用的技巧不管多么高明,如果它很难被理解的话也是毫无意义的。因为这个系统总有一天会走出他的适用范围,这个时候就需要修改和维护其代码。如果这个系统很难维护,那么,就算它当初的设计很高明,但也走到了生命的尽头,逃脱不了被重写的命运。
这些不是大多数人该争论的话题,就像有的“老程序员”曾经总抱怨国内买不到一本关于shader的书,其实他们连基本的程序都写不对
以不变应万变是相对的,而随机应变则是绝对的。 也就类似于静止是相对的,而运动与变化是绝对的,这样一个哲学道理。 因此,从这个角度上将,修改或重写代码都是绝对会发生的。而我们尽力所要保证的重用性,其实都是相对的重用性。因此,有必要考虑,是否真的需要花费大量的精力,去保证这种相对的可重性。 我之所以要这样讲,是因为近几年,可重用性被强调得太多太过。以至于人们忘了自己真的需要做什么。
我对 C 和 C++都很喜欢,但是更喜欢用 C,因为比较简单。C++也很好,只是许多特性都用不上,我喜欢用最简单的办法去解决实际的问题,并尽量保证其合理性。做设计应当以实际问题为出发点,作抽象是好的,但是任何事物都有适用范围,没有人能够写出完全通用的、普适的代码,用 C++ 也做不到。这类似于以不变应万变和随机应变之间的关系。C++程序员有时候太过执著于抽象设计,执著于通用库的设计,执著于可重用,执著于面向对象,以至于忘了设计的根本是为了解决实际问题,忘了除了面向对象的设计方法还有更多其他的设计方法,忘了设计的简洁,忘了设计是否容易被他人所理解,忘了除了重用,还有更多需要注意的地方。C++使人们在设计时,在面向对象、抽象和可重用上背上了太多包袱,以至于许多人沉溺其中,而没有更多的精力去做更多的事情。 因此,从实际出发,分清主次,做自己真正该做的事情才是关键。感觉可重用和面向对象被强调得太过,以至于人们迷失了方向。
就我个人使用的经验而言:我们不仅要看到C和C++语法上的差别和设计上的差别,还要看到编译器的实现。多年前曾经做过一个vc/g++/SUN CC的移植。三个编译器对C++的处理,尤其是模板上有很大的区别;细节上的区别更多。C++对编译器要求太高,而C就要简单很多很多。这是我目前很少使用c++的一个重要原因。另外,在我的工作中,C++能做的工作,在c中一样可以实现,而且语法上更简单,代码更精炼。要是说道c不擅长的地方,我遇到的是在处理具有多层次继承关系时(例如UI等),用c的struct表达就比较困难。
游戏我还是喜欢即时战略的,比如 war3, RPG 或者是策略类的游戏小时候很喜欢,现在觉得一点意思都没有。网络 RPG 也很没有意思,就是练级。只有暴雪的魔兽争霸堪称经典,无论是画面,音效,可操作性,游戏性,战略,竞技,都是最出色的。百万不厌,我不是职业万家,但也玩了3年了,真是经典。
与C++相关的争论真是火啊! 且看SevenCat早些日子的文章 《C++的衰落和JAVA、C#的崛起》 http://blog.vckbase.com/bastet/archive/2007/08/31/17244.html#28959
看了半天,个人感觉这句话最有感触: “愚以为或许语言的进化应该是类似 C->Lua这种,而不是C->C++++这种.”
不参与争论。 不过美空军F-22,F-35项目不再用ada了,现在是c++
十年前你再用汇编开发核心代码,今天用C做开发!佩服!
其实我就有孟老大说的那种情况,每每使用string这些,心理感觉总是很炫,不知道这句代码后面,到底意味着什么。
Posted by: 張傑 | September 11, 2007 12:22 PM 樓主,你知道世界上已經有了幾億行C++代碼?有多少重要的系統,比如.net系統,多少國防,民航,核電站控制系統,是用C++寫的? ----------------------------- 我想知多少國防,民航, 核電站控制系統,是用C++寫的. 快拿資料出來吧.
操,仔细看看c++之父自己说的为什么要创建c++。论起对C语言的掌握程度,估计各位都不如他,毕竟能在Bell Labs呆了几十年。 再说了,绝大多数c或c++程序员,基本都是同时用的,根本没有太大的区别。非要分出c和c++谁更好,就象你非要和你爸爸比谁的家族血统最正规一样。
用C++的人大部分都有一个心智上的问题,以至会抵触,鄙视一些自己不喜欢的东西。儿子太顽皮,东搞搞西搞搞,爸爸教训一下不务正业的儿子,儿子就不认爸爸了,甚至要把爸爸消灭,爸爸也很生气,发誓要给儿子更严厉的教训.... 这是我看完这些消息的结论。
我93年学的C++,学会了之后才正式学C,一路过来,发现C确实是比C++更容易写出优美的代码,但我还是在使用C++,只是高级特性用得越来越少。对于资源的管理,越来越倾向于静态分配的设计。
回复 Rome 的一个问题: 如果我有几个大段的函数,里面只有几个地方有一句相同的话是不一样的(类似于p++=a和p--=a等),而我只想维护一份函数的代码。这时按说可以把这一句代码写成不同的函数,然后用函数指针传进去。但是这句话是在一个很大的内循环中,而这个函数对效率要求很高,所以用函数指针的方法是不可取的。 这样的情况据我所知C++可以用模板,传进一个函数对象来实现。虽然生成的机器码不会少,但是至少我只用维护一份代码。问题是如果我想用C该怎么实现呢? 回答:因为这几个函数只有内部的几句话不同,所以可以增加一个函数的参数,取不同的值来区分不同的执行语句。
大家都很热情,可是,我们都被云风设计进来了,他明明知道这种问题没有啥么意义,还是拿出来招风,看来是为了炒作博客,鉴定完毕!
抽象过程中是需要牺牲的,这很正常.不过对大众来说抽象一点好.对少数精英来说,他们不喜欢受限制.都没错.
楼主,你知道世界上已经有了几亿行C++代码?有多少重要的系统,比如.net系统,多少国防,民航,核电站控制系统,是用C++写的? 你没有感觉C++的魅力,也许你只是用C++写C代码,就好比你可以开着奔驰去菜市场买菜,你觉得不如骑自行车方便,那不是奔驰的错。 请楼主写本Design Pattern in C的书?你会发现你不得不实现C++编译器的工作,你将发明轮子。 Linux鼻祖的这番话,就是程序设计语言上的反智思潮,就是《阿甘正传》,蠢人干蠢事,但越傻越成功? C++基本上就是C + OO,说C++不如C,就是说OO是垃圾,就是抱残守缺,固步自封,拒绝与时俱进,开历史的倒车。 Linux鼻祖说的话就是真理?就要奉为圭臬?我看他就是一个C时代的顽固的遗老遗少,他的梦想就是语言不再进步,精通C就可以混吃混喝独领风骚一统江湖一辈子。然而技术进步的步伐是不以人的意志为转移的,是生产实践和科技内在规律推动的,C早已是明日黄花,大江东去,浪淘尽,千古风流人物,包括Linux鼻祖这位。 据说某位程序设计大师晚年就非常喜爱C++,人家是计算机科学家级别的,比Linux鼻祖要高明N倍。
大家用C#吧
To nothanks: 包裹函数一般不会是程序的瓶颈吧 :-)
To nothanks, C语言里用宏可以解决这个问题。 声明一个宏,该宏其实是一个函数体。该宏带一个或多个参数(取决于有多少语句不一样),参数接收p++=a;之类的语句,然后在函数体里做宏替换。 #define FuncName(param) \ void Func(...)\ { \ ... your code... \ param \ ... other code... \ } 笨办法,比较丑陋也不方便调试。。不过确实是个办法。。
借着这个机会,我想问一个困扰我很久的问题,希望各位高手解惑。先声明一下,我的水平有限:) 如果我有几个大段的函数,里面只有几个地方有一句相同的话是不一样的(类似于*p++=a和*p--=a等),而我只想维护一份函数的代码。这时按说可以把这一句代码写成不同的函数,然后用函数指针传进去。但是这句话是在一个很大的内循环中,而这个函数对效率要求很高,所以用函数指针的方法是不可取的。 这样的情况据我所知C++可以用模板,传进一个函数对象来实现。虽然生成的机器码不会少,但是至少我只用维护一份代码。问题是如果我想用C该怎么实现呢?
虽然微软的MFC,COM这两样东西我一样都不喜欢.不过没办法,开发的需要,还是得用啊.不然你让我怎么写代码.
剑道与编程之道                  作者:scottier   剑道, 只是我从小到大从各种各样的媒体中得出的印象.   因为喜欢剑术, 所以想做一个剑客. 在还没有做剑客时, 就想像自己有一把好剑, 威风凛凛地站在风中, 身边的树叶飘飘......, 一幅很酷的样子. 做了剑客后发现, 想找一把 好剑不容易, 于是, 行侠江湖的几年中, 在找一把好剑.   终于过了几年, 有点胡子了, 有个和尚或老道看你有几分仁义的样子, 指了个好剑的方向. 结果, 你以前做梦都在想像的举剑一睹出现了, 当然, 是给自己看的, 注意, 要披一件大衣, 找个风口, 别忘了边上该有很多枯叶.POS摆完, 也该练点真功夫了, 要不真浪费了这么多年的 追求, 又被别人数落个"金玉其表"的评价. 于是你练呀练呀, 总算有一天, 有一个你不认识的 人叫了你一声"高手". 听第一遍时你还以为他在叫别人. 那天晚上你睡觉都在偷笑.   然后, 你发现与人交手时, 的确顺手了很多. 每与一个曾经的对手交手后,你的信心都更增 加一份. 你非常的爱护那把剑, 所以你常在无人的时候, 在林子里舞剑, 学习怎样提高你的剑 气. 日子又过了很久, 你的剑气与日俱增,现在, 江湖中到处传颂你那"威风凛凛地站在风中, 身边的树叶飘飘......,一幅很酷的样子". 很多的小年青因为你, 也立誓想做一个剑客.   终于有一天, 你发现自己很无聊. 在你眼中, 天下已没有什么高手了, 而自己的剑术好像 也没有办法提高了. 你只是觉的闷, 奇怪天下居然还有你能做到头的事, 但是你隐约中又觉的 自己还差一些. 你非常的爱护那把老剑, 你已能做到人剑一体了. 只有在一个人舞剑时, 你才 能略感欣喜. 那种当初寻剑的回忆,那些练剑的回忆, 那些护剑的回忆, 在舞剑时, 一幕幕地 划过脑海. 在别人看来, 你已是天下第一剑客了, 你的举手投足无不说明, 你是天下第一的. 直到有一天早上醒来, 你发现剑没了.这种事总是传的很快, 那些平日里惧怕你的对手, 又开 始在江湖中兴风作浪起来了, 有几次你与他们偶遇上时, 几乎拼的要死, 才逃走. 失去了剑等 于失去了你自己. 江湖上到处都在找那把你失去的剑. 又是一场无止境的血战. 你突然觉的非 常的无趣, 原来练剑一辈子, 剑一丢, 等于前功尽弃. 你找个安静的山谷, 想修隐起来, 人生 哪, 真是一个没有定数的人生哪. 你在那谷儿中看鱼虫跃, 鸟儿飞, 一直想找到自己失剑后会 退步的这么惨的原因.   有一天, 你像往常一样出游, 在谷中闲逛, 有一只蛇从后面飞速地刺向你, 你在惊觉中, 猛地转身, 用手一挥, 眼前出现的是, 蛇被劈成了两截. 你顿悟,剑术中差的那一点正是"有剑 似无剑, 无剑似有剑". 原来, 那把好老剑, 正是你在习剑中不可逾越的一个障碍. 你不禁感 叹: 人生哪, 真是奇妙的人生哪.   你总算是悟到了剑道. ==================================================================================   程序, 现在是我的职业, 一个差不多快被世间无数热爱或不热爱的人做烂的职业.   因为喜欢程序, 所以想做一个程序员. 在还没有做程序员时, 就想像自己有一台好机子, 用着牛B的C/C++, 十指飞快地在键盘上移动, 最后一个大回车.....看到程序运行起来时, 很 满足的样子. 做了程序后发现, 想有一台好机子, 想找一个好公司不容易, 刚习程序时, 编译 环境最好是字符或图形化的, 在命令行上敲make, cc带一堆的参数, 真的很恐怖. 后来到了 WINDOWS下, 当然是VC,把工具栏上的按键设成自己有用的几个, 开个全屏模式, 最大化地利用 屏幕,多爽. 可是机子是公共的, 有时候累了往边上一躺, 就听到同事蹑手蹑脚地在你的位置 坐下, 按了几下MOUSE, 开始打FF8. 休息了一阵, 有了感觉, 想爬起来开工, 但一看到同事那 幅沉醉的样子, 偶而还转过头来, 冲你一笑, 报歉地说这儿没有存盘点. 唉, 不容易, 谁叫他 那台机子WINDOWS下只剩500MB的空间,而且只是6326的显卡呢. 又躺在了床上, 想着, 要是有一 台机子多好, 没有一点空间是冗余的, 都只有我想要的东西装在硬盘里面, 什么时候有灵感, 什么时候就爬起来开工. 于是, 程序的几年中, 在找一台好机子.   终于过了几年, 有了自己的笔记本, 真爽啊, 可以随便移动, 想在哪写就在哪写.装UNIX 装LINUX装WINDOWS装上C/C++, 笔记本摆在哪儿都不一样, 摆在破桌上,也让人想到一张美国 西部"破桌, 笔记本, 通辑犯的海报, 手枪", 很浓的金黄色调的宣传画. 真酷. POS摆完, 也该练点真功夫了, 要不真浪费了这么多年的追求, 又被别人数落个"金玉其表"的评价. 于 是你练呀练呀, 总算有一天, 有一个你不认识的人叫了你一声"高手". 听第一遍时你还以为他 在叫别人. 那天晚上你睡觉都在偷笑.   然后, 你有新任务时, 的确顺手了很多. 以前要用一天写的代码, 现在只要一个早晨了, 每完成一个完整的程序, 你都更增添了一份信心. 你不知疲倦地用C/C++写和各种各样的程序, 你坚信, 只有用C/C++的程序员才是真正的程序员. 你有点蔑视VB, DEPHI, 用它们写程序, 光是启动程序都让你觉的无法忍受, 更别提那些不简练的语法了. 用C/C++的水准越来越高, 你几乎可以用它来做任何事, 加上一点硬件, 你让你家的电饭堡每天6点开煮, 电视晚上7点开 播, 还有指纹锁......有一天有一个你倾慕已久的水瓶座MM去你那儿玩, 被你家的半自动化所 折服, 惊叹的爱上了你.   终于有一天, 你发现自己很无聊. 在你眼中, 程序没有什么新意了, 无非就是空间与时间 的平衡, 而自己的程序好像也没有办法提高了. 你只是觉的闷, 奇怪天下居然还有你能做到头 的事, 但是你隐约中又觉的自己还差一些. 你非常的爱护那台赛扬老机, 你已能做到人机一体 了. 只有在用它写自己想玩的游戏时, 你才能略感欣喜. 那种当初学C/C++的回忆, 那些攒机的 回忆, 那些护机的回忆, 在键入{}时, 一幕幕地划过脑海. 在别人看来, 你已是天下第一程序 员了,你的举手投足无不说明, 你是天下第一的. 直到有一次旅游回来, 你发现屋子被撬了,没 有一样东西留下.这种事总是很影响你的程序思路的, 那些平日里出现的问题, 又开始在新的 任务中使你痛苦万分了, 有几次你与它们偶遇上时, 几乎想的要死, 才想出来解决之道. 你常 在用程序的过程中想用一个以前写的工具时, 却发现没有了, 要重头写过. 那种累的感觉一下 子冲上心头, 但是你又不想用别人写的东西, 你根本不相信别人写的东西里面没有BUG. 你觉的 非常的无趣, 原来程序一辈子, 机子一丢, 等于前功尽弃. 你辞了工作, 想休息一阵, 人生哪, 真是一个没有定数的人生哪. 你路过图书馆时, 看着那些年青的大学生直直地盯着屏幕,脸上 整一幅痴迷的表情, 脑中一直想找到自己失机后, 写程序变的举步为艰的原因.   有一天, 你在大学中闲逛, 你走在两个看起来像是大二的男生后面, 他们中的一个听起来 水平更高一些, 因为他可以自己用C写一个俄罗斯方块, 另一个说: 哇塞, 这么利害, C语言我 一直觉的很难, 像那些指针什么......", 水平更高一些的说: 我觉的你才利害, 用QUICK BASIC 那么不方便的东西都可以写出俄罗斯方块......". 你顿悟, 是呀, 你一生追求只用C/C++, 是为了什么? 程序只是用来解决实际问题的. 你不必拘泥在一种语言中而看不起别的语言, 你不必拘泥在自己的程式中而不用别人的工具呀. 原来, 语言成了你最大的障碍, 你不禁感叹: 人生哪, 真是奇妙的人生哪.   你又找了份工作, 我们后来看到, 你在任何一台电脑中, 任何一种环境下, 任何一种语言, 甚至有时不用编程, 都可以解决所面对的任何问题.   你总算是悟到了编程之道.
好吧,我来唱唱反调。 学习期。 c,简单,干练。简单到没什么好学的。有用的技能都是在实际运用中获得exp然后lvup的。可以这么说,初学者可以很快得掌握c,但是基本上却什么都不能做。立竿不能见影。这是c的简单干练所决定的,是必然的。 c++,复杂,强大。c++的这些特点已经无须论证。遍地开花的垃圾c++程序员用铁一般的事实说明了一切。针对于与c的比较,仍然来说说立竿见影的问题。我的观点是,虽然这一点c++做得比c好一些,但是仍然不能达到立竿见影的程度。 结论,就这个阶段来说,不分胜负。 开发期。 在任何软工项目都处于“硬件受限”的时代和环境下,c是毫无疑问的王者。(当然,如果你脑容量可堪负荷,请使用asm) 但是在现在,情况分2种。 第1种,我是一个独裁者。当我需要清楚地知道我的代码做了什么的时候,c->asm仍然是最适合的合作者。这种人多半是战斗在“硬件受限”的原始时代。Linus就处于这样一种时代,所以他不得不用c。不要告诉我linux可以跑在多么牛B的机器上,内存可以多么的大,这是p话,linux的kernel必须不能用尽一切可以触及的资源,因为它只是一个承载其他东西的方舟,他必须委屈自己假装是在一个超级受限的环境下工作,把美味的梨子让出来给依赖着它的兄弟姐妹们。所以,kernel类的开发者毫无疑问是战斗在一个“硬件受限”的原始时代,c->asm是他们最好的合作者。 ps.游戏引擎也算半个“硬件受限”环境。虽然不需要承载如kernel般繁多的东西,但是引擎最终将被用来产生实作品,从设计者的角度出发,也必须在一定规模——没错,就是规模——上考虑到由这个引擎所生产出来的东西将要产生的负荷——这毫无疑问地成为了一个“受限”的环境。——但是尽管如此,c->asm也不一定就是最优解,在我的观点来看,这是语言无关的——当然,目前可以做出的选择不多,就个人而言,我仍然选择了c++。 第2种,我是一个追求高产的商人。在性能要求不太严苛的情况下,c就是一个渣。太多的东西需要自己去做,这意味着将会带来冗长的开发周期,这会导致成本的急剧增长,包括各种可量化的(人力物力财力)和不可量化的(团队稳固性团队士气)成本。因此,在这种环境下,c就是一个渣。c++毫无疑问比c做得好。但是,在这个领域里,c和目前的c++都已经失去了辉煌的宝座,新生代的高级语言都拥有给他们致命一击的实力。 结论,从表面来看,仍然是不分胜负。但是c在这一阶段具有稳定、明确的应用领域,算是小胜吧。 运行期。 仍然是分成2种情况,效率关键和效率不关键的。在避开其他环节的情况下,前者当然比后者更受欢迎。 但是,运行期的效率直接与开发期的质量相关,因此运行期的事又是不可能和开发期完全隔开的。 “不管用什么样的语言,都可以写出糟糕的程序。”——这话也可以反过来说,“用任何一种语言,都可以写出优秀的程序。” 因此,在我眼中,这仍然是一个语言无关的问题。 不要把失败的理由放在你无法控制的地方,优秀的进化者会改变自己适应环境。——这是我的观点,因此,我更希望自己能成为“用任何一种语言,都可以写出优秀的程序”这样一个开发者。我认为,每一个以成为优秀开发者为目标的程序员,都应该以这样一种精神为指导,虽然不一定要确实地做到,但是应该具备这样一种精神。用更容易理解的话来说,就是要做到手中有剑心中无剑(请注意这跟武侠小说上的说法是反的-_-!) 结论,既然都语言无关了,当然没有胜负之说。 维护期。 OK,这实际上是一个软工项目中生命周期最长的阶段。 这里涉及了很多东西,最主要的就是3个方面:调试、维护、复用。 这三个议题每一项都可以大书特书再书还要书。 既然是生命期中最长的一个阶段,因此c对c++的重量级攻击放在这里当然就会很有效果。 在维护上的代价而言,结构过程化的c比抽象对象化的c++便宜太多了。 这是“结构过程化”与“抽象对象化”的根本不同所造成的差别。 用程序员们更能够理解的方式来说,c就像一个链表,要增减head很方便;而c++就像一个动态数组,要insert[0]或者remove[0]就要牵一发而动全身! 但是,c真的就很好维护么?非也! 一个疯狂使用#define的c程序,不会比一个滥用oo特性的c++程序更好调试和维护。 因此,这仍然是与开发期工作的质量息息相关的。 结论,好吧,我不得不说,在我眼中c和c++仍然不分胜负。 综上所述,c和c++在我眼中不分好坏,具有同等的地位。 他们分别代表了两种不同的编程思想。 “结构过程化”的c带来的好处是赋予程序员更为强大的控制力,和维护期可以“断章取义”的灵活性——但是代价是更多你不得不亲历亲为的工作。 “抽象对象化”的c++带来的好处是更为贴近现实的思维要求,以及更具亲和力的“人机交互接口”——当然,代价是需要你练好足够的基本功来了解c++在背后到底做了些什么,以及在维护期和复用阶段你可能要面临的“抽筋”式的痛苦。 作为一个拥有美好愿望的程序员,我希望有一种语言,既能给我c的强大控制力和维护期的灵活性,又能给我c++的亲和力和强大的——好吧,我承认我比较喜欢template那种拐弯抹角的东西——脑力训练(—_,—),然后,又能轻易地满足KISS的原则——这将可以让我非常简单地找到高质量的合作者——毕竟怪物级的c/c++程序员不是像现在的本科生一样遍地都是。而且基于c/c++的灵活性——这里叫做不确定性更好——这一特征,每个怪物级的c/c++程序员都有很大可能不能跟另一个怪物互相咬合他们脑袋里高速转动的齿轮——如果你非要那么做,这很可能会带给你更多的机会让你的项目走火入魔,甚至停摆,最后以自爆收场。 但是遗憾的是,目前这只能是一个美好的愿望而已,我不得不采取折中的办法来找一些代替品。 因此,目前我的做法是,用c的规则来写c++的程序,略微地用一些可以被我完全控制的c++的特性(模板的编译期编程技术很赞,可以为运行期的效率和正确性带来很大的好处),最根本的出发点是建立在获得强大控制力和可预期的维护期工作量的目的之上的,当然,还有不能忽视的效率问题——我的目标是一个可扩展的游戏引擎。 胡言乱语了一堆也不知道说了些啥……最后做个总结性发言吧:c和c++都不是什么好东西,但是正如windows也不是什么好东西一样,我们却非得要用它们——至少在可以预见的一段不会算短的时期内。 ---------------------------------------------------------------------------------------------------- 另外,撇开c/c++的比较说点跑题的话。 c++目前确实处于一个相当尴尬的境地,高不成低不就,过于复杂庞大的身躯又成为了他能够被更熟练掌握的门槛。c++目前有两条路可走,一是朝c退过去,二是朝更高之处攀登。但是无论走哪一边,都是“强敌环视”,要想闯出一片新的天空,恐怕需要剑走偏锋了。至于是不是一定要偏着走,偏又要怎么个偏法,我也说不出个所以然来,且让我们拭目以待吧。 而c,很可能将会止步于“硬件受限”的时代吧,然后在这个时代和环境下再一点一点地进化,最终与c++将来的终点完全分道扬镳。 个人以为,程序语言的发展以后将会明确地分出两个方向,一个是以c为代表的“底层亲和”的语言,它们的特点是将给于程序员最大的控制能力,让一切尽在程序员的掌握之中。另一个将是以不断发展的新兴高级语言为代表的方向,也可能是c++以后的方向,它们的特点是不断地将底层的东西从程序员的眼前隐藏起来,让程序员的门槛降得更低,充分地体现出KISS原则,并从而提高生产力和生产效率。 人类的知识累积将随着时间的流逝慢慢增长,如果没有一种有效的途径让后来者可以从更高的地方起步的话,光是学习就足以耗尽人的一生了。因此我认为,“隐藏底层的东西”,将成为今后应用软件技术发展的关键,说白了就是让程序员傻瓜化。 嗯。。。以上两段发言仅代表个人观点:) 反调。
引起争论的话题就不再回复了。我跟 redsea 讨论一下具体问题吧: "从另外一个角度讲, C++ 的方式更白盒, 会让 API 的使用者更能够理解模块内部的一些逻辑关系, 从而更有可能使用正确 --- 代价就是, 逻辑关系一旦变化, 就比较麻烦." 我在这个问题的看法是,C++ 的方式不仅更白盒,往往还更高效。但是 C 在处理这些问题时,坦承一些可能有效率问题的地方(比如函数调用),而把实现严格的隐藏起来。有趣的是,这样做的结果是,日后的更改反而有更大提高效率的余地。 "API 上如果出现下面的问题, 云风你是怎么处理呢 ? API 上某些功能需要获取某个锁之后才能访问, 某些功能不能在持有某个锁的时候访问 ? 某些功能集只有状态机处于某个状态才能调用, 处于另外一个状态无法调用." 这些都属于状态机的问题。我暂时还没有碰到,或者说在即将碰到的时候想办法避免掉了。这总可以通过提高 API 的层次或降低 API 的层次做到。C 语言在实现软件的功能时,大多不会超过三个层次。而最上面的层次现在流行嵌入动态语言来实现。 如果真无可避免,我想我还是会传递一个状态机结构进去。
嘿嘿,我倒觉得失败的设计天天赶工,成功的设计天天翻工
“...唯一真正重要的部分是设计...”Linus大师一语道破天机! 到了一定程度似乎就是在学设计了,成功的设计事半功倍,失败的设计天天翻工......
我终于明白了一句话:高手说什么都是对的,新手说什么都是错的。因为他们的理解层次不同。
看了,谢了。 我非专家,但我更喜欢用C语言。
写留言的时候,请不要在段落前空格。否则会加上 pre 标签。 分段请用两个回车。
这里竟然不自动换行,还老是提交错误,啥么博客.
其实,不论大师还是菜鸟,做人方面都一样,就像我喜欢探险类型的游戏,就总是爱批评赛车游戏很无聊一样,不过就是不同时期不同想法而已,不过,从产品层次来看,能够满足你设计要求的就是好语言.
八卦一句,好像云风的文采变好了~~~ 很惭愧的说,我现在用C实在写不出能抽象的代码来,在C++的帮助下倒还勉强可以糊弄一下。不过C++真的复杂,尤其是最近一个项目,MFC做界面,然后使用Boost,然后两种截然不同的编程风格,然后慢慢陶醉想像以后我的代码的维护者会是什么表情~~
其实用C实现一个程序,也是有一些模式的,这些模式决定了如何使用C的某些突出的特性。写C程序如果能用好宏与函数回调,就足以满足很多程序了。用C的好处就是,我能掌握每一块内存在哪里。
没办法, 工作需要刚刚由C转向C++。 大体感觉吧C++真复杂,似乎与KISS原则背道而驰(一直在linux环境开发,KISS我一直信奉, 这下迷茫了;-) )。 不过《设计模式》的确很诱人, 真在疯狂学习啊,同样与KISS原则不同,似乎每个对象都得搞个抽象…… ~~迷茫~~
谈论了那么多还是请大家不要忘记C不过是C++的一个子集.用不用C++的那些特性是使用者自己在经过考虑过后得出的.自己选择的东西最后反过来批判只能说明当初悟道不深亦或者现在还未悟道.语言所做的只是提供一些认为可以在某些情况某些环境下能够帮助使用者更好编码的特性.记住,用不用在于你自己,谁也没有强迫你非要那样或这样.不过是提供多一种选择罢了.所以所有讨论的焦点不是语言的好坏而是你为什么做出这样的选择.
突然有盲人摸象的感觉
楼主的帖子可以总结为一句话“使用C++设计代码不如C” 进而颠覆了C++的设计目标“抽象、封装和代码重用” 那么这些目标不正确么?回答当然是否定的,否则就不会有java和C#大行其道。 那么有10软件开发经验,使用C++编写了数以10W计的代码的楼主,为什么会转而选择C而弃用C++呢?对于linus教主的言语会产生如此强烈的共鸣? 回答这个问题,可以从二人从事的工作上得到答案。楼主是国内著名游戏引擎的开发者,而linus则是专注于os内核的开发者和维护者。他们进行软件开发的设计决策都是以“性能”优先的。 孟老大说,linus曾经试图使用C++写内核,后来失败了;而楼主也把使用C++完成的游戏引擎重新用C来写。我想可能是面对同样问题的时候,设计决策影响了他们对语言的选择,毕竟C所擅长的,C++不如。 但是世界上有如此之多的成功的C++软件项目,是否使用C重写能够带来更好的效果呢? C++仍然是C的进化,虽然它包含了太多的东西使它难以掌握,但是它仍然变得越来越好。特别是C++0x中可能做出对于线程模型和GC的修订,有助于使用者更好的完成设计。
1.我很牛 2.我写C++很牛 3.可是我写C比写C++更牛 4.我和linus一样牛
说那么多都是浮云,不如上点具体的例子或者代码什么的比较能说明问题。
又见语言之争。不知道云风用了C以后提高了多少生产效率?减少了多少BUG?提高了多少运行速度?
居然还是从刘江的网页上看到这里的新链接,算是头一回。平时我第一个打开的都是这里啊。。。好好学习学习
我一直都很喜欢C语言,因为我觉得唯一一个我可能完全把握的就只有C语言了。C++现在在用,但是经验并不多,平时还是喜欢printf打印信息。 C语言中对我帮助很大的书除了KR的,就是《C语言接口设计与实现》了,觉得作者对C的抽象真是用到了及至!
不是很明白,也许只有深刻理解C、C++,并长年的实践体会才能明白吧。
再来几句,语言的进化一定是为了能写出逻辑复杂度更高,用现在的眼光来看更为庞大的应用。为了达到这个目标,抽象能力是最关键的一点,因为这能帮助程序员理解复杂的系统。所以,如果是做较为大型的应用的话,不管用什么语言,抽象能力都是很关键的。不要看windows的api都是c接口,那是为了兼容(其实也并非都是c接口,很多高级应用早就使用com了,现在又有了.net)。不用害怕重写或者重新设计接口,重构是不可避免的,只要每次重构能离目标更近。如果兼容是很重要的,那么就保留旧模块,然后设计新接口新模块,象directx做的那样,不是挺好的。
博主说的没错,关键是设计。我也是个喜欢简洁的人,但我还是在用c++,因为c++的资源太多了。我现在倾向于这样使用c++,使用c++的抽象能力做设计,定义模块接口,这里只用虚函数,不用其它复杂的高级特性,类似com的做法;然后在实现模块时程序员爱怎么写就怎么写,只用c也可以,呵呵。
说了这么多也没见人贴个链接,刘的翻译很多地方并没有体现linus的意思,推荐去新闻组看原文: http://thread.gmane.org/gmane.comp.version-control.git/57643/focus=57918 看完就知道这完全是两个层次之间掐架 :)
根据个人的体会,文中很多观点我很赞同,不过有一点实在难以理解: “C 和 C++ 都可以选择重写设计失败的部分,但不一样的是, C 程序员几乎可以不考虑妥协的问题。”设计改变了,相关代码需要重写,这是无法避免的,跟用什么语言无关吧。为什么C在这方面付出的代价会比C++低得多,以致于“几乎可以不考虑妥协的问题”?
愚以为或许语言的进化应该是类似 C->Lua这种,而不是C->C++++这种.
同样的设计思路来做一件工作, C/C++ 在模块api 级别的区别: 1. C 的话, 模块之间用 free function, function point, handle, struct 来定义接口 2. C++ 用 interface (abstract class), class 来定义接口 C++ 的方式是耦合度更高的方式, 哪些function 属于哪个 class, class 之间的关系, 出现在了 API 这层, 导致了云风说的修改框架带来的麻烦问题. C 的 API 方式耦合度更低些. 从另外一个角度讲, C++ 的方式更白盒, 会让 API 的使用者更能够理解模块内部的一些逻辑关系, 从而更有可能使用正确 --- 代价就是, 逻辑关系一旦变化, 就比较麻烦. API 上如果出现下面的问题, 云风你是怎么处理呢 ? 1. API 上某些功能需要获取某个锁之后才能访问, 某些功能不能在持有某个锁的时候访问 ? 2. 某些功能集只有状态机处于某个状态才能调用, 处于另外一个状态无法调用.
有什么证据可以证明C++所犯下的这些错误C就不会呢,只是因为C++提供了更多的方式所以就更容易犯错?
事实上,我昨天写的编码规范就规定了继承只能出现在 cpp 文件中而不能出现在头文件中,继承的作用范围最多不能超过它所位于的 cpp 文件。
把 C++ 看成是比 C 更低级的语言,还有一个暗示,就是在接口上,用 C 函数加结构,而不用带复杂继承结构的类。因为,继承也被我看成是一种在模块内部实现中便于代码复用的语法糖,而不是模块间的设计层面的东西。
我被当头一棒之后最大的感悟就是,应该把 C++ 看成是比 C 更低级的语言。这样,使用 boost 和模板的时候就不会因为它们带来的坏味道而有负罪感了。
我也是昨天看到 Linus 的文章的,对我来说是当头一棒啊。 我看完了那篇文章时候就在我的网易博客写了两篇日志。第一篇是公开承认我之前错了,第二篇是我的 C++ 编码规范,把 C++ 每一个特性能否使用,在什么场合使用都列举出来。 第二篇文章我没用公开,是私人日记。因为我还没有实际按那个编码规范来写程序(从今天才开始),还不保证行得通。 之所以马上就写了这个编码规范,就是因为 Linus 那句话“你当然可以用任何语言编写糟糕的代码。但是,有些语言,尤其是带有一些心智(mental)包袱的语言本身就非常糟糕。”
基本来说C++太复杂了,很多特性用不到,可能是我比较初级吧,不过感觉C++的抽象能力要想用同样的C实现出来,并不是短时间就能达到并超越的。 因为新手可以用面向对象语言写出过程化程序,而老手可以用过程化语言写出面向对象的程序。
abstraction是好的,关键在于是否能用好,是否能透过abstraction看清本质。这篇文章http://calculist.blogspot.com/2007/09/science-and-engineering.html 比我总结的好。 C++野心太大,希望包罗万有,以致语义实在是太复杂了。而这个复杂性在我看来是不必要的,因为它只增加了程序员的负担,却没有带来新的思维方式。 设计语言做减法而不是做加法,并不是往一门流行语言上堆砌一系列特性就能得到好语言。OO也不是唯一的抽象方式,functional programming就很不错。

Post a comment

非这个主题相关的留言请到:留言本