C++的坑真的多吗?

C++的坑真的多吗?

先说明一下,我不希望本文变成语言争论贴。希望下面的文章能让我们客观理性地了解C++这个语言。(另,我觉得技术争论不要停留在非黑即白的二元价值观上,这样争论无非就是比谁的嗓门大,比哪一方的观点强,毫无价值。我们应该多看看技术是怎么演进的,怎么取舍的。)

事由

周五的时候,我在我的微博上发了一个贴说了一下一个网友给我发来的C++程序的规范和内存管理写的不是很好(后来我删除了,因为当事人要求),我并非批判,只是想说明其实程序员是需要一些“疫苗”的,并以此想开一个“程序员疫苗的网站”,结果,@简悦云风同学直接回复到:“不要用 C++ 直接用 C , 就没那么多坑了。”就把这个事带入了语言之争。

我又发了一条微博

@左耳朵耗子 新浪个人认证 : 说C++比C的坑更多的人我可以理解,但理性地思考一下。C语言的坑也不少啊,如果说C语言有90个坑,那么C++就是100个坑(另,我看很多人都把C语言上的坑也归到了C++上来),但是C++你得到的东西更多,封装,多态,继承扩展,泛型编程,智能指针,……,你得到了500%东西,但却只多了10%的坑,多值啊

结果引来了更多的回复(只节选了一些言论):

  • @淘宝褚霸也在微博里说:“自从5年前果断扔掉C++,改用了ansi c后,我的生活质量大大提升,没有各种坑坑我。
  • @Laruence在其微博里说: “我确实用不到, C语言灵活运用struct, 可以很好的满足这些需求.//@左耳朵耗子: 封装,继承,多态,模板,智能指针,这也用不到?这也学院派?//@Laruence: 问题是, 这些东西我都用不到… C语言是工程师搞的, C++是学院派搞的

那么,C++的坑真的多么?我还请大家理性地思考一下

C++真的比C差吗?

我们先来看一个图——《各种程序员的嘴脏的对比》,从这个图上看,C程序员比C++的程序员在注释中使用fuck的字眼多一倍。这说明了什么?我个人觉得这说明C程序员没有C++程序员淡定

Google Code 中程序语言出现 fuck 一词的比率

不要太纠结上图,只是轻松一下,我没那么无聊,让我们来看点真正的论据。

相信用过C++的程序员知道,C++的很多特性主要就是解决C语言中的各种不完美和缺陷:(注:C89、C99中许多的改进正是从C++中所引进的

  • 用namespace解决了很C函数重名的问题。
  • 用const/inline/template代替了宏,解决了C语言中宏的各种坑。
  • 用const的类型解决了很多C语言中变量值莫名改变的问题。
  • 用引用代替指针,解决了C语言中指针的各种坑。这个在Java里得到彻底地体现。
  • 用强类型检查和四种转型,解决了C语言中乱转型的各种坑。
  • 用封装(构造,析构,拷贝构造,赋值重载)解决了C语言中各种复制一个结构体(struct)或是一个数据结构(link, hashtable, list, array等)中浅拷贝的内存问题的各种坑。
  • 用封装让你可以在成员变量加入getter/setter,而不会像C一样只有文件级的封装。
  • 用函数重载、函数默认参数,解决了C中扩展一个函数搞出来像func2()之类的ugly的东西。
  • 用继承多态和RTTI解决了C中乱转struct指针和使用函数指针的诸多让代码ugly的问题。
  • 用RAII,智能指针的方式,解决了C语言中因为出现需要释放资源的那些非常ugly的代码的问题。
  • 用OO和GP解决各种C语言中用函数指针,对指针乱转型,及一大砣if-else搞出来的ugly的泛型。
  • 用STL解决了C语言中算法和数据结构的N多种坑。
(注意:上面我没有提重载运算符和异常,前者写出来的代码并不易读和易维护(参看《恐怖的C++语言》后面的那个示例),坑也多,后者并不成熟(相对于Java的异常),但是我们需要知道try-catch这种方式比传统的不断地判断函数返回值和errno形成的大量的if-else在代码可读性上要好很多)

上述的这些东西填了不知有多少的C语言编程和维护的坑。少用指针,多用引用,试试autoptr,用用封装,继承,多态和函数重载…… 你面对的坑只会比C少,不会多。

C++的坑有多少?

C++的坑真的不多,如果你能花两到三周的时候读一下《Effecitve C++》里的那50多个条款,你就知道C++里的坑并不多,而且,有很多条款告诉我们C++是怎么解决C的坑的。然后,你可以读读《Exceptional C++》和《More Exceptional C++》,你可以了解一下C++各种问题的解决方法和一些常见的经典错误。

当然,C++在解决了很多C语的坑的同时,也因为OO和泛型又引入了一些坑。消一些,加一些,我个人感觉上总体上只比C多10%左右吧。但是你有了开发速度更快,代码更易读,更易维护的500%的利益。

另外,不可否认的是,C++中的代码出了错误,有时候很难搞,而且似乎用C++的人会觉得C++更容易出错?我觉得主要是下面几个原因:

  • C和C++都没学好,大多数人用C++写C,所以,C的坑和C++的坑合并了。
  • C++太灵活了,想怎么搞就怎么搞,所以,各种不经意地滥用和乱搞。

另外,C++的编译对标准C++的实现各异,支持地也千差万别,所以会有一些比较奇怪的问题,但是如果你一般用用C++的封装,继承,多态,以及namespace,const, refernece,  inline, templete, overloap, autoptr,还有一些OO 模式,并不会出现奇怪的问题。

而对于STL中的各种坑,我觉得是程序员们还对GP(泛型编程)理解得还不够,STL是泛型编程的顶级实践!属于是大师级的作品,一般人很难理解。必需承认STL写出来的代码和编译错误的确相当复杂晦涩,太难懂了。这也是C++的一个诟病。

这和Linus说的一样 —— “C++是一门很恐怖的语言,而比它更恐怖的是很多不合格的程序员在使用着它”。注意我飘红了“很多不合格的程序员”!

我觉得C++并不适合初级程序员使用,C++只适合高级程序员使用(参看《21天学好C++》和《C++学习自信心曲线》),正如《Why C++》中说的,C++适合那些对开发维护效率和系统性能同时关注的高级程序员使用。

这就好像飞机一样,开飞机很难,开飞机要注意的东西太多太多,对驾驶员的要求很高,但你不能说飞机这个工具很烂,开飞机的坑太多。(注:我这里并不是说C++是飞机,C是汽车,C++和C的差距,比飞机到汽车的差距少太多太多,这里主要是类比,我们对待C++语言的心态!)

C++的初衷

理解C++设计的最佳读本是《C++演化和设计》,在这本书中Stroustrup说了些事:

1)Stroustrup对C是非常欣赏,实际上早期C++许多的工作是对于C的强化和净化,并把完全兼容C作为强制性要求。C89、C99中许多的改进正是从C++中所引进。可见,Stroustrup对C语言的贡献非常之大。今天不管你对C++怎么看,C++的确扩展和进化了C,对C造成了深远的影响

2)Stroustrup对于C的抱怨主要来源于两个方面——在C++兼容C的过程中遇到了不少设计实现上的麻烦;以及守旧的K&R C程序员对Stroustrup的批评。很多人说C++的恶梦就是要去兼容于C,这并不无道理(Java就干的比C++彻底得多,但这并不是Stroustrup考虑的,Stroustrup一边在使尽浑身解数来兼容C,另一方面在拼命地优化C。

3)Stroustrup在书中直接说,C++最大的竞争对手正是C,他的目的就是——C能做到的,C++也必须做到,而且要做的更好。大家觉得是不是做到了?有多少做到了,有多少还没有做到?

4)对于同时关注的运行效率和开发效率的程序员,Stroustrup多次强调C++的目标是——“在保证效率与C语言相当的情况下,加强程序的组织性;能保证同样功能的程序,C++更短小”,这正是浅封装的核心思想。而不是过渡设计的OO。(参看:面向对象是个骗局

5)这本书中举了很多例子来回应那些批评C++有运行性能问题的人。C++在其第二个版本中,引入了虚函数机制,这是C++效率最大的瓶颈了,但我个人认为虚函数就是多了一次加法运算,但让我们的代码能有更好的组织,极大增加了程序的阅读和降底了维护成本。(注:Lippman的《深入探索C++对象模型》也说明了C++不比C的程序在运行性能低。Bruce的《Think in C++》也说C++和C的性能相差只有5%)

6)这本书中还讲了一些C++的痛苦的取舍,印象最深的就是多重继承,提出,拿掉,再被提出,反复很多次,大家在得与失中不断地辩论和取舍。这个过程让我最大的收获是——a) 对于任何一种设计都有好有坏,都只能偏重一方,b) 完全否定式的批评是不好的心态,好的心态应该是建设性地批评

我对C++的感情

我先说说我学C++的经历。

我毕业时,是直接从C跳过C++学Java的,但是学Java的时候,不知道为什么Java要设计成这样,只好回头看C++,结果学C++的时候又有很多不懂,又只得回头看C最后发现,C -> C++ -> Java的过程,就是C++填C的坑,Java填C++的坑的过程

注,下面这些东西可以看到Java在填C/C++坑:

  • Java彻底废弃了指针(指针这个东西,绝对让这个社会有几百亿的损失),使用引用。
  • Java用GC解决了C++的各种内存问题的诟病,当然也带来了GC的问题,不过功大于过。
  • Java对异常的支持比C++更严格,让编程更方便了。
  • Java没有像C++那样的template/macro/函数对象/操作符重载,泛型太晦涩,用OO更容易一些。
  • Java改进了C++的构造、析构、拷贝构造、赋值。
  • Java对完全抛弃了C/C++这种面向过程的编程方式,并废弃了多重继承,更OO(如:用接口来代替多重继承)
  • Java比较彻底地解决了C/C++自称多年的跨平台技术。
  • Java的反射机制把这个语言提升了一个高度,在这个上面可以构建各种高级用法。
  • C/C++没有一些比较好的类库,比如UI,线程 ,I/O,字符串处理等。(C++0x补充了一些)
  • 等等……

当然时代还在前进,这个演变的过程还在C#和Go上体现着。不过我学习了C -> C++  -> Java这个填坑演进的过程,让我明白了很多东西:

  • 我明白了OO是怎么一回事,重要的是明白了OO的封装,继承,和多态是怎么实现的。(参看我以前写过的《C++虚函数表解析》和《C++对象内存布局》)
  • 我明白了STL的泛型编程和Java的各种花哨的技术是怎么一回事,以及那些很花哨的编程方法和技术。
  • 我明白了C,C++,Java的各中坑,这就好像玩火一样,我知道怎么玩火不会烧身了。

我从这个学习过程中得到的最大的收获不是语言本身,而是各式各样的编程技术和方法,和技术的演进的过程,这比语言本身更重要在这个角度上学习,你看到的不是一个又一个的坑,你看到的是——各式各样让你可以爬得更高的梯子

我对C++的感情有三个过程:先是喜欢地要死,然后是恨地要死,现在的又爱又恨,爱的是这个语言,恨的是很多不合格的人在滥用和凌辱它。

C++的未来

C++语言发展大概可以分为三个阶段(摘自Wikipedia):

  • 第一阶段从80年代到1995年。这一阶段C++语言基本上是传统类型上的面向对象语言,并且凭借著接近C语言的效率,在工业界使用的开发语言中占据了相当大份额;
  • 第二阶段从1995年到2000年,这一阶段由于标准模板库(STL)和后来的Boost等程式库的出现,泛型程式设计在C++中占据了越来越多的比重性。当然,同时由于Java、C#等语言的出现和硬件价格的大规模下降,C++受到了一定的冲击;
  • 第三阶段从2000年至今,由于以Loki、MPL等程式库为代表的产生式编程和模板元编程的出现,C++出现了发展历史上又一个新的高峰,这些新技术的出现以及和原有技术的融合,使C++已经成为当今主流程式设计语言中最复杂的一员。

在《Why C++? 王者归来》中说了 ,性能主要就是要省电,省电就是省钱,在数据中心还不明显,在手机上就更明显了,这就是为什么Android 支持C++的原因。所以,在NB的电池或是能源出现之前,如果你需要注重程序的运行性能和开发效率,并更关注程序的运性能,那么,应该首选 C++。这就是iOS开发也支持C++的原因。

今天的C++11中不但有更多更不错的东西,而且,还填了更多原来C++的坑。(参看:C++11 WikiC++ 11的主要特性

 

总结

  • C++并不完美,但学C++必然让你受益无穷。
  • 是那些不合格的、想对编程速成的程序员让C++变得坑多。

最后,非常感谢能和“@简悦云风”,“@淘宝诸霸”,“@Laruence”一起讨论这个问题!无论你们的观点怎么样,我都和你们“在一起”,嘿嘿嘿……

(全文完)


关注CoolShell微信公众账号和微信小程序

(转载本站文章请注明作者和出处 酷 壳 – CoolShell ,请勿用于任何商业用途)

——=== 访问 酷壳404页面 寻找遗失儿童。 ===——
好烂啊有点差凑合看看还不错很精彩 (46 人打了分,平均分: 4.43 )
Loading...

C++的坑真的多吗?》的相关评论

  1. 我代表“很多不合格的程序员”表示,“我对C++的感情只有二个过程:先是喜欢地要死,然后是恨地要死”
    最早学的语言就是C++,觉得学会了就无所不能。结果学用起来总是把自己折磨的要死,复杂得要死才是真相。
    自从C++转向纯C,再也没痛苦过了。而且现在偶尔吐糟一下C++的各种坑,还觉得精神倍爽!

    客观地讲,就是C++为了填坑带来的复杂性超过了我这种的“不合格的程序员”容忍程度。
    所以我宁愿接受C少许的美中不足,也不愿意补一个坑就引入数个更大的坑外加学习复杂度
    引用一句我比较赞同的编程语言的设计哲学,scheme语言的:Programming languages should be designed not by piling feature on top of feature, but by removing the weaknesses and restrictions that make additional features appear necessary.

  2. C++真心不好用。但是一个字符串就有string,wstring还有原始的char[]等各种表现形式。更别说第三方库还会用神马QString,CString,真心晕得要死。字符串编码的问题就更不说了。

  3. 严重同意楼主的看法,各种语言都有坑,还是看自己如何避免吧
    不能一味吐槽

  4. c++应该弃掉兼容c的部分,变为真正完全面向对象的语言 正是因为兼容c,再加上自身的改进,导致了c++庞大而复杂

  5. 这篇文章和前面的文章对比的一看,有很明显的主观性,看来博主是一位对c++有着深厚情感的fans。

  6. 额…本人正在入门,还是先把C学好再说,我可不希望我是不合格的程序员祸害人间

  7. C++ 程序员都不觉得 C++ 那么多坑. 觉得 C++ 坑多的只能说是 C 程序员++.
    习惯性地认为 C++ 是 A better C 或者 C with classes 才会抱着 C 来跟 C++ 比较, 其实这完全是两种语言.
    C++ 兼容 C 当年是出于营销考虑的吧 (玩笑), 同意 @purevirtual 的说法, 如果再弄个语言保留 C++ 的长处删去对 C 的一些兼容就很好了. 尤其是指针. 特别是指针 cast.

  8. “是那些不合格的、想对编程速成的程序员让C++变得坑多”;但是说来我学编程也这么久了,现在都不敢说自己会C++,偶尔想实现什么高级功能还要到网上查一查。这门语言庞杂的特性让人实在做不到触类旁通,融汇贯通,除非坚定地使用它的一个子集,否则一不小心就错了。这还得算是C++的一大坑。

  9. “不是有坑,是因为你不会”,这理由真是不怎么样。
    这结论等同于在说“你哪种语言写的熟就用哪种吧。”

  10. 我也曾经学过一段时间的C++,但是放弃了,太复杂。
    在C和C++ 两个门派中,我坚定的用户C。
    我们最近在培训写出更好更安全,bug更少的C代码。
    讲了一堆技巧,其实根本技巧就是 你要对的起C语言对你的信任。

  11. Marvin :C++不应该得到赞美,因为它没有完成的它的历史定位。面向对象+组件平台,都完成的一塌糊涂。而需要Java,.Net这类货色充当历史舞台。而面对性能问题,又会回来到C上来解决。C的问题很明显,但这么多年来还不能退休就是因为C++不能接班。所以你们不觉得这个情况很尴尬吗?

    历史定位?请问这个历史定位是你定的吗?C++不能接C的班的地方多了去了,C++使用模板等特性以后很难确保获得安全的能在内核态运行的代码…

  12. 我个人认为c++的学习要从标准c++开始学起,不要一开始就接触qt之类。
    bs的那本书非常值得一看。

  13. ytj :C++坑大了#########
    我生活中主要用Python,C++作为第二语言,用在性能瓶颈上。但我觉得 C++ 坑很大,标准委员会过于学术化,而且效率低下,,N年才出一部标准,同时期Python已经更新换代得面目全非。甚至有《Imperfect C++》这么厚厚一本书专门抱怨C++的不足。

    Python的确不仰仗于委员会,而是仰仗于一个贤明的独裁者,好在这个独裁者还没打算退休,不过光是2.x和3.0之争就够书一笔了吧,又何必嘲讽C++的委员会设计呢。
    关于《Imperfect C++》这本书,我相信你是读过的,你真的认为这是“专门抱怨C++不足”的一本书吗?

    最大的问题就是实在过于复杂,而且很多时候没法避免复杂性。这个帖子我超同意:http://coolshell.cn/articles/7771.html

    C++最大的问题是把复杂性统统对使用者暴露了出来,以泛型为例,使用Java和C#编程的人当中对这两门语言的泛型机制真正了解的可能不多,他们知道的仅仅是一个神奇的

    没有反射========

    如果你能提议一种匹配得上C++的高性能反射方案,我相信标准委员会虽然官僚但仍然乐于接受。

    很多真正有用的东西却没有,比如反射。当新手发现检查一个类型是否有 swap(T&a) 成员函数,以便针对特定类型使用高效的交换操作,却需要使用SFINAE这种看上去跟需求豪不搭杆的技术,不傻眼才怪。而Python只需要hasattr(T, “swap”)即可。
    http://stackoverflow.com/questions/87372/is-there-a-technique-in-c-to-know-if-a-class-has-a-member-function-of-a-given

    哦,这是我见过的最肤浅的语言比较:“我经常使用的A语言能做xxx,而B语言竟然不提供这支持——B语言竟然不能让我使用A语言的思考方式来使用它,B语言真实渣”。
    很遗憾,你似乎并没有理解generic typing,对于习惯了动态语言思维的人而言,将其看作一种静态的duck typing其实是很方便的,而SFINAE只是一个静态的“method missing”罢了。

    代码噪音多==========

    绝大多数是由不合格的——特别是C程序员出身的——C++写手造成的。

    代码胜过千言万语,C++11中写一个泛型的min函数都不简单::
    #include
    templateauto min(T x, U y)-> typename std::remove_reference< decltype(x ::type{ return x < y ? x : y; }
    而相比之下,Python中只需要::
    def min(x, y):return x if x < y else y

    你能告诉我这简短的代码背后,Python为你做了哪些事情吗?

    实际上C++完全可以设计成::
    []min(x, y) { return x < y ? x : y }

    很高兴C++不是由你来设计,否则它的坑一定比现在多十倍…

    别问为什么不用宏。这里有详细讨论,解释了为什么要像上面那么写::
    http://cpp-next.com/archive/2011/11/having-it-all-pythy-syntax/

    模板编程语法奇特================

    你是说比用强制缩进来体现代码逻辑还奇特?语言评论帖里总会出现各种沙文主义的言论,真是有意思的现象。

    为了实现某些静态特性,函数式编程显得过于奇特。引入D语言的 “static if” 会好很多。
    出错信息========
    C++ 拥有令人发疯的出错信息,就像一只猫刚爬过键盘一样,因此还诞生了很多 Errormessages pettyprint 工具。现在 Concept 也被否决,看来 C++ 标准委员会认为这不是个大问题。对于经验丰富的程序员来说也许不是,因为他们已经习惯读天书了,但是如果新手程序员尝试对一个 list 容器使用 std::sort ,肯定会被吓坏。

    Concept被否决是因为现在的设计还不够成熟,出错信息的设计不包含在C++的设计当中,实际上很少有哪个语言的出错信息对新手用户是友好的。

    不使用设计良好的Loki::SmartPtr作为标准智能指针类型==================================================
    个人觉得Loki::SmartPtr比boost的智能指针设计要好,可是C++标准委员会却用劣币驱逐良币。

    我真是佩服你的自大,Andrei本人作为有影响力的一员,你认为…我想我还是省略下面的话比较好…

    STL===
    我承认自己是一个STL Hater,这玩意儿坑太多,比如”vector, auto_ptr, etc”说一些不那么常见的坑:

    你其实完全不熟悉STL,何必硬要凑上呢?你的评论洋洋洒洒,但总结起来只有一句话:我不熟悉or我不喜欢==不好。

    string 的 API————-
    std::string API及其丑陋,成员函数太多。其实可以借鉴D语言的一个特性:如果“d.function(argv)“编译不通过,就尝试“function(d, argv)“。这样就解决了语法一致与封装的问题。

    这个提议不错,不过string被人诟病的难道不是成员函数太少?

    接口不一致———-
    算法与容器松散固然是好事,但有时我也奇怪,为什么不能写 sort(sth) 而必须用sort(sth.begin(), sth.end()) 或者 sort(sth, sth + sizeof(sth)/sizeof(sth[0]))虽然一个简单的函数重载就可以办到。而且有 list::sort 却没有 vector::sort 或者deque::sort ,使得代码重构时,把 list 换成 vector 有可能要在天书一般的编译错误中寻找问题。
    你可以找得到 count 和 count_if, remove 和 remove_if, replace 和 replace_if, 等等。但是却只有 copy 而没有 copy_if,难道STL的发明者害怕他们带来的惊奇还不够么?
    list, deque 都有 push_front 成员,vector 却没有。也许有人会说,vector 没法实现push_front,可是真的没法实现吗?实际上 push_front(v) 可以用 insert(begin(), v)实现。正确的说应该是不能在常数时间复杂度实现,可是这并不能成为不提供 push_front的理由。也许 C++ 标准委员会认为提供 push_front 可能在复杂度上起到迷惑人的作用,毕竟其他容器的 push_front 和 push_back 都是常数时间,毕竟真要 push_front 用insert 代替的话不容易令人误解。所以决定选择复杂度一致而不是接口一致。不过这仍然存在某种程度上的不一致,参见下一条。
    时间复杂度不一致—————-
    大家都知道在 STL 中 vector、string、deque、map 的成员函数 size 是常数时间复杂度的,可 list 的 size 却是线性复杂度 (至少 SGI STL 和 libstdc++ 是这种实现)。不过这也是没办法,要不然 splice 没法做到常数复杂度的。在这里 C++ 标准却又选择接口一致而不是复杂度一致。而且我觉得 list 提供一个 size 也没必要,毕竟 size() 可以用distance(begin(), end()) 代替,而且事实上也有不少标准库的实现是这样做的, list也不能用索引,size 操作的意义不大。

    很遗憾你完全没有领会STL的设计思想,只是在按照自己的喜好做臧否而已,这样对你还是对看你评论的人其实都没有好处,尤其是那些对C++并不熟悉的人,你的评论纯粹是种误导,建议你看了Essential of Programming之后再来声讨STL的设计

    容器没有公共基类,而且不能用来继承———————————-

    你是说像Java那样?请问你要一个有公共基类的STL容器准备有何妙用?

    当然,为了可能根本用不上的功能,而添加虚拟函数,对于性能狂 STL 来说其抽象惩罚是不可接受,尽管其他语言都无此顾虑。但事实上,可以设计出一个能够给用户一个选择,让他们自由决定是否使用公共基类和虚拟继承的容器,以便既满足对性能要求苛刻的用户的需求,又满足普通用户的需求。实现方法可参考《C++ Templates: The Complete Guide》Section 16.4. Parameterized Virtuality。

    相信我,如果真的做了这样的设计,你一定会口风一转改为声讨C++设计复杂,还需要用户做许多不必要的决定,我想你这时候光顾喷的爽已经忘记你刚刚还称赞“Less is More”来着。
    你所谓的继承机制是OO Diagram,而对于Generic Diagram的STL来说,需要的只是一个typename T而已。当然你对泛型了解的肤浅早已暴露,所以不怪你。

    结语====
    其实我个人觉得D语言本身好过C++语言,这东西可是Andrei Alexandrescu 设计的。可惜这个世界上大多数代码和库基础都是建立在C++上。

    嗯,我相信这个世界上大多数代码和库是建立在C,Shell和Perl上的,而不是C++,关于C和C++的恩怨是非,那可以另开一个贴了。

    不能说只有专家才能用好C++,更准确的应该是C++常常把简单问题复杂化。C++标准委员会真应该从 Java, Python, C#, D 引用一些特性。

    C++从Java引进了一个不错的特性,就是range-based for loop的语法,还有像Java一样建立了一个一致的内存模型,从C#里也颇有借鉴,毕竟Herb Sutter本人是C++/CLI的架构师 ,至于D,有Andrei在,我想不必担心。可是你能解释一下Python这个私货是什么?

  14. 依据作者提出的观点可以总结出:关键在于学cpp可以让你收获很多。我想知道,这是收获在学其他的语言时候不能获得吗?能不能更快,更好的获得呢?

  15. C++在国内外备受非议已经有很多年历史,国内以游戏界编程偶像云风的某篇博文为首,掀起了轰轰烈烈的反C++复C运动,国外则以Linus的一篇牛粪开头的帖子为起点重燃战火。在C++社区的周围,使用各种语言的社区正在结成一个包围网,来围剿这门备受“争议”的语言。

    其实在围剿C++的包围网(不知道我为啥总想起信长包围网)当中,各路动态语言的新贵,基本是来秀自己的便利语法的优越感的,对于这类攻击的回应倒还很简单,C++是以性能和大项目组织见长,和这些货色走的不是一套路线。

    接下来的攻击主要来自Java社区(也会有.NET社区的一些活力掺杂其间),Java社区所秉持的优势主要有三大宝具: 简单傻瓜易用(包括IDE),跨平台,库和框架丰富而庞大。但是Java社区一直无法直面的一个问题是:性能,无论多么优秀的JIT把多么复杂的数学运算优化得和本地代码一样快,作为产品的Java代码始终无法摆脱迟缓和臃肿。这是语言的功劳,也是语言的过失——我相信萨丕尔·伍尔夫假说在程序设计界是成立的——Java引以为傲的让程序员更专注于逻辑带来的是程序员对性能的忽视,好吧,现在硬件便宜,这正是IBM之流的硬件商最喜欢听到的…

    对于这些基于虚拟机/运行时的语言,性能始终是其无法承受之痛,于是C扩展这一救命稻草粉墨登场了。C扩展救得了他们吗?有时能,当然付出的代价,是打破其吹嘘的安全性,以及丧失了它们轻快的语法而拖泥带水的C代码..美好的虚拟机上的世界和虚拟机下的泥泞的现实就这么凑合在了一起,只有.NET和Lua在一旁笑而不语。

    在包围网当中,对C++攻击最狠,最具代表性的还是C社区。C社区的意见大致可以总结为三点:
    1. C++的性能我能达到
    2. C++太复杂,不如C简单
    3. 动态语言性能的不足,都可以依靠C来填补
    针对这三点,C++社区自然也有应对:
    1. 不仅性能,C能做的,C++都能做
    2. 如果愿意,C++能做到和C一样简单,而C++具有的表达能力,C永远也无法企及
    3. 参见1,同时C++甚至能做到更好,比如Luabind
    基本上,双方在这些观点上是想吃不下的,最后只能归结为哲学,信仰,审美,等等形而上的层面。
    可以说,对C++的围攻中,最有杀伤力的就是来自C的攻击,因为这是一场旷日持久的圣战,嗯,使用这个词不是故意煽情,而是这的确是观念之争。

    下面的故事来自我自己的亲身经历,我们从中能看出C和C++程序员的矛盾是如何的不可调和,而所谓的C程序员对C++和C++程序员又是如何的包含恶意:

    有这样一个项目J,大概有十几年历史了,项目中混合使用了C和C++。但是关于项目的代码风格和约定,基本上是一份C的代码风格和约定,关于C++的内容几乎没有。当你打开其中的C++代码,你就会发现,其中的代码大部分是形如:

    class foo {
    int a, b, c;
    bool init();
    bool close();
    bool do();
    };

    这样的风格,事实上,存在着一份不成文的C++风格约定:
    不要使用模板,和任何与模板有关系的东西,比如STL,Boost,禁止使用标准库,不仅是STL,即使是ANSI-C的标准库也尽量不要使用,禁止任何运算符重载,异常,禁止使用引用作为函数参数类型——必须使用指针,禁止使用static关键字,除非是用来声明文本域可见的函数,禁止使用命名空间,用char *表示字符串,禁止使用new和delete(这一禁令的理由最为黑色幽默,因为new和delete和导致在Linux上对libstdc++.so的依赖,而部分Linux发行版上默认不会安装这个库…)可能还有很多禁止,但我记不太清了,实在是太多了,但很奇怪,就是没有禁止使用C++而采用纯C。

    作为一个C++程序员,当第一次遭遇代码评审的时候,噩梦袭来,依照上述不成文的惯例,一份还算是符合现代C++精神的代码被改成了儿童不宜的一坨,在据理力争之下,唯一的引用参数终于在拷贝构造函数里保存了下来,new和delete也被替换成了经过包装的malloc和free——按照惯例,他们为每一个类编写一个staitc newInstance函数来代替new,相应的,在编写一个不static的kill函数来代替delete——实际上是在对象析构前就对调用 free( this )!!!!!!!——更绝的是,当他们看到在构造函数中分配资源而在析构函数中释放资源的代码的时候,那种看见了Nyarulatohotip一样的表情,真应该让Lovecraft来看看…为了向他们解释这种他们头一次听说的名叫RAII的idiom,最后不得不搬出了Java 7中引入的try-with-resource statement作为参照,他们总算明白了这是一种自动管理资源的技巧,当然对于什么叫异常安全就别指望能在一天下午同时科普了….

    看到这里,诸位看官或许认为我编造了一个天方夜谭,然而事实上,这是我前几周的亲身经历,那些无比仇恨C++的伟大的C程序员来自一个著名企业的Java虚拟机核心团队,他们当中有人是工作十多年的“富有经验的开发者”(至少经理们是这样认为)。这次伤透了心的代码审查给那个倒霉的C++程序员带来的是大量的修改工作(同时还得对各种异味忍者恶心)——以及代码提交的延迟。对此大为光火的TechLead(他当然无条件的支持来自总部的“资深专家们”的意见)说出了一句发人深思而又富含哲理的偈语:

    “C++代码的可读性是很糟糕的,因为你把C++代码拿给(几乎只懂C的)C程序员读的时候,他们都看不懂或者会搞错”。(可是为什麽不干脆禁用C++呢,像Linus做的那样?)

    在下认为这个故事生动的反映了C++在业界的应用状况,All valid C program is valid C++ program(K&R, Practice of Programming),但是,Not all valid C programmer is valid C++ programmer. C is the sin of C++。对于C++最恐怖和致命的伤害,不是来自于他和其它语言比较的差异,而是来自于他无法将那些Substandard的C程序员排除在外,而C可以反过来这么做,只需要在代码里定义几个用C++关键字命名的核心结构就行了,比如class,template等等。这是一个劣币驱逐良币的过程,C++仅仅作为a better C是没有前途的,第二系统综合症足以要了它的命。

    要克服C这一原罪,C++需要更进一步的进化,希望我能活着看到那一天…

  16. 相对c来说,c++开发超过5万行以上的代码还是比较有优势的,
    如果熟悉使用boost智能指针,stl容器来管理内存,内存管理也不是什么大问题。
    用C的一大问题是,如果不使用面向对象的开发方法,代码规模大的以后很难控制,如果使用面向对象的开发,C语言提供的东西对开发者来说,太少了。

    语言诞生的时代与当时软件开发理论有很深的联系。 c诞生的年代,使用面向对象去开发软件不是主流,所以用C语言,却用面向对象去开发,完全是硬把2种不相关的东西用在一起。

  17. 看到云风用abi兼容性来攻击C++,我就笑了,abi东西在一个开源项目中是问题吗?在商业项目中,内部模块之间用的着考虑abi兼容性?要考虑abi兼容性这东西的场合,一般就是开发平台级库和商用动态库需要考虑,实际需要考虑的场合不多。
    就算要考虑abi兼容性,难道不能用纯C作接口,用c++作实现?当然是指规模大的项目,这方面有一个开源项目sipxecs,百万级别的开源项目,做的很好,C做接口,c++写实现。
    一些人为了攻击C++,无所不其用,不管用的上的用不上的都拿来当虎皮扯。

  18. 我赞同博主所说的填坑顺序,我是顺着学下来的,感觉c++除了要记得东西多一些,别的还行,java也学得很顺,我遇到过上来就学java的,而且也没什么计算机基础,和我抱怨java太难了,我说c++能搞死你。果然后来学c++痛苦的要死。学c的时候又小痛苦了一下

  19. 论运行效率,编译效率,c++不及c,论开发效率,又不及ruby这类高级语言。
    c++流行只是因为c的流行,c的流行是因为操作系统都用c写,api接口能完美兼容。
    用了七八年的c++了,并且现在还在用,然而对它的感觉是从:惊喜->喜爱->厌恶,这么一个过程。

    如果要做底层开发,就用c,如果要做高层开发,就用java,ruby,python这种语言。

  20. @icosagon
    我现在做的一个项目就是用c++开发堆积起来的,快500万行了。到了这个级别的app,我认为oo只会让整个系统越搞越混乱。

    B.T.W
    用服务器级别的机器去编译整个项目,也得一个多小时。以效率高而著称的c++,在此刻是如此的让人觉得讽刺。

  21. @Rasefon
    oo会让整个系统越搞越乱?现在大规模软件设计方法被你一个项目否定了?
    500万行的项目,难道你准备用面向过程的方法进行设计?
    500万行的项目,没有一个好的设计,或者不做重构,无论用什么语言,都只会越搞越乱。

  22. 上面有人说了,Linux的设计很好,我倒想知道好在哪里?
    感觉上千万行的代码设计好都是扯谈的。

  23. @icosagon
    不是一个项目的问题。
    比如一个大规模软件,如果你是初始设计者,自然你对你自己的负责的那块东西的整体架构会很熟悉,你会用你喜欢的模式去设计,会用到很多oo的特性。然后你离职了,来了几个新人,他们对你的设计一无所知,需要花费更多的时间去了解这些架购的结构,而不是应用本身的业务逻辑。比起c这种纯粹的过程式语言,我没发觉c++有什么优势。人员的流动对于整个软件行业来说很普遍。
    而且对于c++来说,几乎没人愿意用它去写底层,比如驱动程序,我举个例子,windows的打印机的驱动,最早就是一些DDI的接口,后来微软把它封装了一层,用c++做了个壳子,做成了com。结果呢,对于各个厂商而言,底层的库还是用c写的,只是会额外多做很多无用功,并且引入新的bug。
    而对于例如web应用这种上层的开发来说,c++也没有优势可言,到现在为止还没看到过一个full stack的web框架是用c++设计并实现的。
    还是那句话,c++只会让你徒增烦恼而已。

  24. 我怎么觉得C++要比Java容易,除了没人用的多继承有点复杂,其他的用法都很简单明了,java还要学线程,网络编程。 那些essential, exceptional, C++编程规范等书籍里面都是几条重复的规则在讲来讲去,很多规则明显就不会用错,居然也可以讲几页。

  25. @Rasefon

    难道用C,用面向过程的方法进行设计,后来的人就能很容易接手?
    至少oo让后来的人还有脉络可寻,从整体入手,了解模块层次划分,调整局部,最后达到可维护的目标。
    500万行项目,使用面向过程的方法?你会让后来的人痛不欲生,因为看上去处处可入手, 一 一对应的功能都有函数可看,但实际上你敢改吗? 你改了根本不知道会影响到其它什么模块。

  26. 这就好像飞机一样,开飞机很难,开飞机要注意的东西太多太多,对驾驶员的要求很高,但你不能说飞机这个工具很烂,开飞机的坑太多。(注:我这里并不是说C++是飞机,C是汽车,C++和C的差距,比飞机到汽车的差距少太多太多,这里主要是类比,我们对待C++语言的心态!)

    ===

    1) 如果你们公司烂人多,就不要让他们开飞机.
    2) 你们公司是不是现在将来都只请50万年薪的人.
    3) 如果不是。。。。

  27. 喜欢C的偏执狂们(比如我),又像用一些比较时髦的语言特性的,看看Vala吧。。。

    至于到底谁好谁外,没啥好争的,至少GNU的项目基本都是C,最多加上C++的bingding。。。。

  28. @icosagon
    对啊,你看我说的就是不同的应用场景c++和其他语言比没优势。
    桌面软件的话,发展到今天,肯定也是像c#这类语言更好用。

  29. Why Vala?

    Many developers want to write GNOME applications and libraries in high-level programming languages but can’t or don’t want to use C# or Java for various reasons, so they are stuck with C without syntax support for the GObject type system. The Vala compiler allows developers to write complex object-oriented code rapidly while maintaining a standard C API and ABI and keeping the memory requirements low.

    C# and Java libraries can’t be used the same way as native GObject libraries from C and other languages and can’t be accepted as part of the GNOME Platform. Managed applications also suffer from usually higher memory requirements which is not acceptable in some situations.

    valac produces C source and header files from Vala source files as if you’ve written your library or application directly in C. Using a Vala library from a C application won’t look different than using any other GObject-based library. There won’t be a vala runtime library and applications can distribute the generated C code with their tarballs, so there are no additional run- or build-time dependencies for users.

  30. YY :

    ytj :C++坑大了#########
    我生活中主要用Python,C++作为第二语言,用在性能瓶颈上。但我觉得 C++ 坑很大,标准委员会过于学术化,而且效率低下,,N年才出一部标准,同时期Python已经更新换代得面目全非。甚至有《Imperfect C++》这么厚厚一本书专门抱怨C++的不足。

    Python的确不仰仗于委员会,而是仰仗于一个贤明的独裁者,好在这个独裁者还没打算退休,不过光是2.x和3.0之争就够书一笔了吧,又何必嘲讽C++的委员会设计呢。
    关于《Imperfect C++》这本书,我相信你是读过的,你真的认为这是“专门抱怨C++不足”的一本书吗?

    最大的问题就是实在过于复杂,而且很多时候没法避免复杂性。这个帖子我超同意:http://coolshell.cn/articles/7771.html

    C++最大的问题是把复杂性统统对使用者暴露了出来,以泛型为例,使用Java和C#编程的人当中对这两门语言的泛型机制真正了解的可能不多,他们知道的仅仅是一个神奇的

    没有反射========

    如果你能提议一种匹配得上C++的高性能反射方案,我相信标准委员会虽然官僚但仍然乐于接受。

    很多真正有用的东西却没有,比如反射。当新手发现检查一个类型是否有 swap(T&a) 成员函数,以便针对特定类型使用高效的交换操作,却需要使用SFINAE这种看上去跟需求豪不搭杆的技术,不傻眼才怪。而Python只需要hasattr(T, “swap”)即可。
    http://stackoverflow.com/questions/87372/is-there-a-technique-in-c-to-know-if-a-class-has-a-member-function-of-a-given

    哦,这是我见过的最肤浅的语言比较:“我经常使用的A语言能做xxx,而B语言竟然不提供这支持——B语言竟然不能让我使用A语言的思考方式来使用它,B语言真实渣”。
    很遗憾,你似乎并没有理解generic typing,对于习惯了动态语言思维的人而言,将其看作一种静态的duck typing其实是很方便的,而SFINAE只是一个静态的“method missing”罢了。

    代码噪音多==========

    绝大多数是由不合格的——特别是C程序员出身的——C++写手造成的。

    代码胜过千言万语,C++11中写一个泛型的min函数都不简单::
    #include
    templateauto min(T x, U y)-> typename std::remove_reference< decltype(x ::type{ return x < y ? x : y; }
    而相比之下,Python中只需要::
    def min(x, y):return x if x < y else y

    你能告诉我这简短的代码背后,Python为你做了哪些事情吗?

    实际上C++完全可以设计成::
    []min(x, y) { return x < y ? x : y }

    很高兴C++不是由你来设计,否则它的坑一定比现在多十倍…

    别问为什么不用宏。这里有详细讨论,解释了为什么要像上面那么写::
    http://cpp-next.com/archive/2011/11/having-it-all-pythy-syntax/

    模板编程语法奇特================

    你是说比用强制缩进来体现代码逻辑还奇特?语言评论帖里总会出现各种沙文主义的言论,真是有意思的现象。

    为了实现某些静态特性,函数式编程显得过于奇特。引入D语言的 “static if” 会好很多。
    出错信息========
    C++ 拥有令人发疯的出错信息,就像一只猫刚爬过键盘一样,因此还诞生了很多 Errormessages pettyprint 工具。现在 Concept 也被否决,看来 C++ 标准委员会认为这不是个大问题。对于经验丰富的程序员来说也许不是,因为他们已经习惯读天书了,但是如果新手程序员尝试对一个 list 容器使用 std::sort ,肯定会被吓坏。

    Concept被否决是因为现在的设计还不够成熟,出错信息的设计不包含在C++的设计当中,实际上很少有哪个语言的出错信息对新手用户是友好的。

    不使用设计良好的Loki::SmartPtr作为标准智能指针类型==================================================
    个人觉得Loki::SmartPtr比boost的智能指针设计要好,可是C++标准委员会却用劣币驱逐良币。

    我真是佩服你的自大,Andrei本人作为有影响力的一员,你认为…我想我还是省略下面的话比较好…

    STL===
    我承认自己是一个STL Hater,这玩意儿坑太多,比如”vector, auto_ptr, etc”说一些不那么常见的坑:

    你其实完全不熟悉STL,何必硬要凑上呢?你的评论洋洋洒洒,但总结起来只有一句话:我不熟悉or我不喜欢==不好。

    string 的 API————-
    std::string API及其丑陋,成员函数太多。其实可以借鉴D语言的一个特性:如果“d.function(argv)“编译不通过,就尝试“function(d, argv)“。这样就解决了语法一致与封装的问题。

    这个提议不错,不过string被人诟病的难道不是成员函数太少?

    接口不一致———-
    算法与容器松散固然是好事,但有时我也奇怪,为什么不能写 sort(sth) 而必须用sort(sth.begin(), sth.end()) 或者 sort(sth, sth + sizeof(sth)/sizeof(sth[0]))虽然一个简单的函数重载就可以办到。而且有 list::sort 却没有 vector::sort 或者deque::sort ,使得代码重构时,把 list 换成 vector 有可能要在天书一般的编译错误中寻找问题。
    你可以找得到 count 和 count_if, remove 和 remove_if, replace 和 replace_if, 等等。但是却只有 copy 而没有 copy_if,难道STL的发明者害怕他们带来的惊奇还不够么?
    list, deque 都有 push_front 成员,vector 却没有。也许有人会说,vector 没法实现push_front,可是真的没法实现吗?实际上 push_front(v) 可以用 insert(begin(), v)实现。正确的说应该是不能在常数时间复杂度实现,可是这并不能成为不提供 push_front的理由。也许 C++ 标准委员会认为提供 push_front 可能在复杂度上起到迷惑人的作用,毕竟其他容器的 push_front 和 push_back 都是常数时间,毕竟真要 push_front 用insert 代替的话不容易令人误解。所以决定选择复杂度一致而不是接口一致。不过这仍然存在某种程度上的不一致,参见下一条。
    时间复杂度不一致—————-
    大家都知道在 STL 中 vector、string、deque、map 的成员函数 size 是常数时间复杂度的,可 list 的 size 却是线性复杂度 (至少 SGI STL 和 libstdc++ 是这种实现)。不过这也是没办法,要不然 splice 没法做到常数复杂度的。在这里 C++ 标准却又选择接口一致而不是复杂度一致。而且我觉得 list 提供一个 size 也没必要,毕竟 size() 可以用distance(begin(), end()) 代替,而且事实上也有不少标准库的实现是这样做的, list也不能用索引,size 操作的意义不大。

    很遗憾你完全没有领会STL的设计思想,只是在按照自己的喜好做臧否而已,这样对你还是对看你评论的人其实都没有好处,尤其是那些对C++并不熟悉的人,你的评论纯粹是种误导,建议你看了Essential of Programming之后再来声讨STL的设计

    容器没有公共基类,而且不能用来继承———————————-

    你是说像Java那样?请问你要一个有公共基类的STL容器准备有何妙用?

    当然,为了可能根本用不上的功能,而添加虚拟函数,对于性能狂 STL 来说其抽象惩罚是不可接受,尽管其他语言都无此顾虑。但事实上,可以设计出一个能够给用户一个选择,让他们自由决定是否使用公共基类和虚拟继承的容器,以便既满足对性能要求苛刻的用户的需求,又满足普通用户的需求。实现方法可参考《C++ Templates: The Complete Guide》Section 16.4. Parameterized Virtuality。

    相信我,如果真的做了这样的设计,你一定会口风一转改为声讨C++设计复杂,还需要用户做许多不必要的决定,我想你这时候光顾喷的爽已经忘记你刚刚还称赞“Less is More”来着。
    你所谓的继承机制是OO Diagram,而对于Generic Diagram的STL来说,需要的只是一个typename T而已。当然你对泛型了解的肤浅早已暴露,所以不怪你。

    结语====
    其实我个人觉得D语言本身好过C++语言,这东西可是Andrei Alexandrescu 设计的。可惜这个世界上大多数代码和库基础都是建立在C++上。

    嗯,我相信这个世界上大多数代码和库是建立在C,Shell和Perl上的,而不是C++,关于C和C++的恩怨是非,那可以另开一个贴了。

    不能说只有专家才能用好C++,更准确的应该是C++常常把简单问题复杂化。C++标准委员会真应该从 Java, Python, C#, D 引用一些特性。

    C++从Java引进了一个不错的特性,就是range-based for loop的语法,还有像Java一样建立了一个一致的内存模型,从C#里也颇有借鉴,毕竟Herb Sutter本人是C++/CLI的架构师 ,至于D,有Andrei在,我想不必担心。可是你能解释一下Python这个私货是什么?

    GOOD

xiaoyu进行回复 取消回复

您的电子邮箱地址不会被公开。 必填项已用*标注