C++的坑真的多吗?
openkk 12年前
<div id="news_body"> <p> 先说明一下,我不希望本文变成语言争论贴。希望下面的文章能让我们客观理性地了解 C++ 这个语言。(另,我觉得技术争论不要停留在非黑即白的二元价值观上,这样争论无非就是比谁的嗓门大,比哪一方的观点强,毫无价值。我们应该多看看技术是怎 么演进的,怎么取舍的。)</p> <p> <strong>事由</strong></p> <p style="text-align:center;"><img title="C Plus Plus" alt="C++的坑真的多吗?" src="https://simg.open-open.com/show/d39585e45f363e96dac4cd4569a5b38a.jpg" width="300" height="240" /></p> <p style="text-align:center;"> 周五的时候,我在我的微博上发了一个贴说了一下一个网友给我发来的 C++ 程序的规范和内存管理写的不是很好(后来我删除了,因为当事人要求),我并非批判,只是想说明其实程序员是需要一些“疫苗”的,并以此想开一个“程序员疫苗的网站”,结果,@简悦云风同学<a href="/misc/goto?guid=4958521768578429435">直接回复到</a>:“<strong>不要用 C++ 直接用 C , 就没那么多坑了。</strong>”就把这个事带入了语言之争。</p> <p> 我又<a href="/misc/goto?guid=4958521768668598685" target="_blank">发了一条微博</a>:</p> <p> <a title="左耳朵耗子" href="/misc/goto?guid=4958521768766669617">@左耳朵耗子</a> <a href="/misc/goto?guid=4958521768853482818" target="_blank"><img title="新浪个人认证 " alt="C++的坑真的多吗?" src="https://simg.open-open.com/show/046c7604a84c0768ef44c7afc2dff647.gif" width="8" height="7" /></a></p> <p>: 说 C++ 比C的坑更多的人我可以理解,但理性地思考一下。C语言的坑也不少啊,如果说C语言有 90 个坑,那么 C++ 就是 100 个坑(另,<strong>我看很多人都把C语言上的坑也归到了 C++ 上来</strong>),但是 C++ 你得到的东西更多,封装,多态,继承扩展,泛型编程,智能指针,……,你得到了 500% 东西,但却只多了 10% 的坑,多值啊。</p> <p> 结果引来了更多的回复(只节选了一些言论):</p> <ul> <li>@淘宝褚霸<a href="/misc/goto?guid=4958521768951872665">也在微博里说</a>:“自从 5 年前果断扔掉C++,改用了 ansi c 后,我的生活质量大大提升,没有各种坑坑我。”</li> </ul> <ul> <li>@Laruence<a href="/misc/goto?guid=4958521769037733749" target="_blank">在其微博里</a>说: “我确实用不到, C 语言灵活运用 struct, 可以很好的满足这些需求.//@左耳朵耗子: 封装,继承,多态,模板,智能指针,这也用不到?这也学院派?//@Laruence: 问题是, 这些东西我都用不到… C 语言是工程师搞的, C++ 是学院派搞的”</li> </ul> <p> <strong>那么,C++的坑真的多么?我还请大家理性地思考一下</strong>。</p> <p> <strong>C++真的比C差吗?</strong></p> <p> 我们先来看一个图——《<a href="/misc/goto?guid=4958521769132396497" target="_blank">各种程序员的嘴脏的对比</a>》,从这个图上看,C程序员比 C++ 的程序员在注释中使用 fuck 的字眼多一倍。这说明了什么?<strong>我个人觉得这说明C程序员没有 C++ 程序员淡定</strong>。</p> <p style="text-align:center;"><img title="Google Code 中程序语言出现 fuck 一词的比率" alt="C++的坑真的多吗?" src="https://simg.open-open.com/show/1e69fd40d44db31152807d7b9e60fa06.jpg" width="543" height="303" /></p> <p> 不要太纠结上图,只是轻松一下,我没那么无聊,让我们来看点真正的论据。</p> <p> 相信用过 C++ 的程序员知道,C++的很多特性主要就是解决C语言中的各种不完美和缺陷:(注:<strong>C89、C99中许多的改进正是从 C++ 中所引进的</strong>)</p> <ul> <li>用 namespace 解决了很C函数重名的问题。</li> </ul> <ul> <li>用 const/inline/template 代替了宏,解决了C语言中宏的各种坑。</li> </ul> <ul> <li>用 const 的类型解决了很多C语言中变量值莫名改变的问题。</li> </ul> <ul> <li>用引用代替指针,解决了C语言中指针的各种坑。这个在 Java 里得到彻底地体现。</li> </ul> <ul> <li>用强类型检查和四种转型,解决了C语言中乱转型的各种坑。</li> </ul> <ul> <li>用封装(构造,析构,拷贝构造,赋值重载)解决了C语言中各种复制一个结构体(struct)或是一个数据结构(link, hashtable, list, array 等)中浅拷贝的内存问题的各种坑。</li> </ul> <ul> <li>用封装让你可以在成员变量加入 getter/setter,而不会像C一样只有文件级的封装。</li> </ul> <ul> <li>用函数重载、函数默认参数,解决了C中扩展一个函数搞出来像 func2()之类的 ugly 的东西。</li> </ul> <ul> <li>用继承多态和 RTTI 解决了C中乱转 struct 指针和使用函数指针的诸多让代码 ugly 的问题。</li> </ul> <ul> <li>用 RAII,智能指针的方式,解决了C语言中因为出现需要释放资源的那些非常 ugly 的代码的问题。</li> </ul> <ul> <li>用 OO 和 GP 解决各种C语言中用函数指针,对指针乱转型,及一大砣 if-else 搞出来的 ugly 的泛型。</li> </ul> <ul> <li>用 STL 解决了C语言中算法和数据结构的N多种坑。</li> </ul> <p> (注意:上面我没有提重载运算符和异常,前者写出来的代码并不易读和易维护(参看《<a title="恐怖的 C++ 语言" href="/misc/goto?guid=4958521769226455399" target="_blank">恐怖的 C++ 语言</a>》后面的那个示例),坑也多,后者并不成熟(相对于 Java 的异常),但是我们需要知道 try-catch 这种方式比传统的不断地判断函数返回值和 errno 形成的大量的 if-else 在代码可读性上要好很多)</p> <p> 上述的这些东西填了不知有多少的C语言编程和维护的坑。<strong>少用指针,多用引用,试试 autoptr,用用封装,继承,多态和函数重载…… 你面对的坑只会比C少,不会多。</strong></p> <p> <strong>C++的坑有多少?</strong></p> <p> C++的坑真的不多,如果你能花两到三周的时候读一下《<a href="/misc/goto?guid=4958521769317717973" target="_blank">Effecitve C++</a>》里的那 50 多个条款,你就知道 C++ 里的坑并不多,而且,有很多条款告诉我们 C++ 是怎么解决C的坑的。然后,你可以读读《<a href="/misc/goto?guid=4958521769412056045" target="_blank">Exceptional C++</a>》和《<a href="/misc/goto?guid=4958521769507758760" target="_blank">More Exceptional C++</a>》,你可以了解一下 C++ 各种问题的解决方法和一些常见的经典错误。</p> <p> 当然,C++在解决了很多C语的坑的同时,也因为 OO 和泛型又引入了一些坑。消一些,加一些,我个人感觉上总体上只比C多 10% 左右吧。但是你有了开发速度更快,代码更易读,更易维护的 500% 的利益。</p> <p> 另外,不可否认的是,C++中的代码出了错误,有时候很难搞,而且似乎用 C++ 的人会觉得 C++ 更容易出错?我觉得主要是下面几个原因:</p> <ul> <li><strong>C和 C++ 都没学好,大多数人用 C++ 写C,所以,C的坑和 C++ 的坑合并了。</strong></li> </ul> <ul> <li><strong><strong>C++太灵活了,想怎么搞就怎么搞,所以,各种不经意地滥用和乱搞。</strong></strong></li> </ul> <p> 另外,C++的编译对标准 C++ 的实现各异,支持地也千差万别,所以会有一些比较奇怪的问题,但是如果你一般用用 C++ 的封装,继承,多态,以及 namespace,const, refernece, inline, templete, overloap, autoptr,还有一些 OO 模式,并不会出现奇怪的问题。</p> <p> 而对于 STL 中的各种坑,我觉得是程序员们还对 GP(泛型编程)理解得还不够,STL 是泛型编程的顶级实践!属于是大师级的作品,一般人很难理解。必需承认 STL 写出来的代码和编译错误的确相当复杂晦涩,太难懂了。这也是 C++ 的一个诟病。</p> <p> 这和 <a href="/misc/goto?guid=4958521769226455399" target="_blank">Linus 说的一样</a> —— “<strong>C++是一门很恐怖的语言,而比它更恐怖的是很多不合格的程序员在使用着它</strong>”。注意我飘红了“<strong>很多不合格的程序员</strong>”!</p> <p> 我觉得 C++ 并不适合初级程序员使用,C++只适合高级程序员使用(参看《<a title="“21天教你学会C++”" href="/misc/goto?guid=4958185568889678838" target="_blank">21天学好C++</a>》和《<a title="C++ 程序员自信心曲线图" href="/misc/goto?guid=4958185568154026079" target="_blank">C++学习自信心曲线</a>》),正如《Why C++》中说的,C++适合那些对开发维护效率和系统性能同时关注的高级程序员使用。</p> <p> <strong>这就好像飞机一样,开飞机很难,开飞机要注意的东西太多太多,对驾驶员的要求很高,但你不能说飞机这个工具很烂,开飞机的坑太多。</strong>(注:我这里并不是说 C++ 是飞机,C是汽车,C++和C的差距,比飞机到汽车的差距少太多太多,这里主要是类比,我们对待 C++ 语言的心态!)</p> <p> <strong>C++的初衷</strong></p> <p> 理解 C++ 设计的最佳读本是《<a href="/misc/goto?guid=4958521769674308123" target="_blank">C++演化和设计</a>》,在这本书中 Stroustrup 说了些事:</p> <p> 1)Stroustrup 对C是非常欣赏,<strong>实际上早期 C++ 许多的工作是对于C的强化和净化</strong>,并把完全兼容C作为强制性要求。C89、C99中许多的改进正是从 C++ 中所引进。可见,Stroustrup 对C语言的贡献非常之大。<strong>今天不管你对 C++ 怎么看,C++的确扩展和进化了C,对C造成了深远的影响</strong>。</p> <p> 2)Stroustrup 对于C的抱怨主要来源于两个方面——在 C++ 兼容C的过程中遇到了不少设计实现上的麻烦;以及守旧的K&R C 程序员对 Stroustrup 的批评。<strong>很多人说 C++ 的恶梦就是要去兼容于C,这并不无道理(</strong>Java 就干的比 C++ 彻底得多<strong>)</strong>,但这并不是 Stroustrup 考虑的,Stroustrup 一边在使尽浑身解数来兼容C,另一方面在拼命地优化C。</p> <p> 3)Stroustrup 在书中直接说,C++最大的竞争对手正是C,他的目的就是——<strong>C能做到的,C++也必须做到,而且要做的更好</strong>。大家觉得是不是做到了?有多少做到了,有多少还没有做到?</p> <p> 4)对于同时关注的运行效率和开发效率的程序员,Stroustrup 多次强调 C++ 的目标是——“<strong>在保证效率与C语言相当的情况下,加强程序的组织性;能保证同样功能的程序,C++更短小</strong>”,<strong>这正是浅封装的核心思想</strong>。而不是过渡设计的 OO。(参看:<a title="面向对象是个骗局?!" href="/misc/goto?guid=4958521769771532993" target="_blank">面向对象是个骗局</a>)</p> <p> 5)这本书中举了很多例子来回应那些批评 C++ 有运行性能问题的人。C++在其第二个版本中,引入了虚函数机制,这是 C++ 效率最大的瓶颈了,但我个人认为虚函数就是多了一次加法运算,但让我们的代码能有更好的组织,极大增加了程序的阅读和降底了维护成本。 (注:Lippman 的《<a href="/misc/goto?guid=4958521769855159774" target="_blank">深入探索 C++ 对象模型</a>》也说明了 C++ 不比C的程序在运行性能低。Bruce 的《<a href="/misc/goto?guid=4958521769956675481" target="_blank">Think in C++</a>》也说 C++ 和C的性能相差只有5%)</p> <p> 6)这本书中还讲了一些 C++ 的痛苦的取舍,印象最深的就是多重继承,提出,拿掉,再被提出,反复很多次,大家在得与失中不断地辩论和取舍。这个过程让我最大的收获是——a) <strong>对于任何一种设计都有好有坏,都只能偏重一方</strong>,b) <strong>完全否定式的批评是不好的心态,好的心态应该是建设性地批评</strong>。</p> <p> <strong>我对 C++ 的感情</strong></p> <p> 我先说说我学 C++ 的经历。</p> <p> 我毕业时,是直接从C跳过 C++ 学 Java 的,但是<strong>学 Java 的时候,不知道为什么 Java 要设计成这样,只好回头看C++,结果学 C++ 的时候又有很多不懂,又只得回头看C</strong>,<strong>最后发现,C -> C++ -> Java 的过程,就是 C++ 填C的坑,Java 填 C++ 的坑的过程</strong>。</p> <p> 注,下面这些东西可以看到 Java 在填C/C++坑:</p> <ul> <li>Java 彻底废弃了指针(指针这个东西,绝对让这个社会有几百亿的损失),使用引用。</li> <li>Java 用 GC 解决了 C++ 的各种内存问题的诟病,当然也带来了 GC 的问题,不过功大于过。</li> <li>Java 对异常的支持比 C++ 更严格,让编程更方便了。</li> <li>Java 没有像 C++ 那样的 template/macro/函数对象/操作符重载,泛型太晦涩,用 OO 更容易一些。</li> <li>Java 改进了 C++ 的构造、析构、拷贝构造、赋值。</li> <li>Java 对完全抛弃了C/C++这种面向过程的编程方式,并废弃了多重继承,更 OO(如:用接口来代替多重继承)</li> <li>Java 比较彻底地解决了C/C++自称多年的跨平台技术。</li> <li>Java 的反射机制把这个语言提升了一个高度,在这个上面可以构建各种高级用法。</li> <li>C/C++没有一些比较好的类库,比如 UI,线程 ,I/O,字符串处理等。(C++0x 补充了一些)</li> <li>等等……</li> </ul> <p> 当然时代还在前进,这个演变的过程还在 C# 和 Go 上体现着。不过我学习了C -> C++ -> Java 这个填坑演进的过程,让我明白了很多东西:</p> <ul> <li>我明白了 OO 是怎么一回事,重要的是明白了 OO 的封装,继承,和多态是怎么实现的。(参看我以前写过的《<a href="/misc/goto?guid=4958521770039050981" target="_blank">C++虚函数表解析</a>》和《<a href="/misc/goto?guid=4958521770143233805" target="_blank">C++对象内存布局</a>》)</li> <li>我明白了 STL 的泛型编程和 Java 的各种花哨的技术是怎么一回事,以及那些很花哨的编程方法和技术。</li> <li>我明白了C,C++,Java 的各中坑,这就好像玩火一样,我知道怎么玩火不会烧身了。</li> </ul> <p> <strong>我从这个学习过程中得到的最大的收获不是语言本身,而是各式各样的编程技术和方法,和技术的演进的过程,这比语言本身更重要</strong>!(<strong>在这个角度上学习,你看到的不是一个又一个的坑,你看到的是——各式各样让你可以爬得更高的梯子</strong>)</p> <p> 我对 C++ 的感情有三个过程:先是喜欢地要死,然后是恨地要死,现在的又爱又恨,爱的是这个语言,恨的是很多不合格的人在滥用和凌辱它。</p> <p> <strong>C++的未来</strong></p> <p> C++语言发展大概可以分为三个阶段(<a href="/misc/goto?guid=4958521770223908042" target="_blank">摘自 Wikipedia</a>):</p> <ul> <li>第一阶段从 80 年代到 1995 年。这一阶段 C++ 语言基本上是传统类型上的面向对象语言,并且凭借著接近C语言的效率,在工业界使用的开发语言中占据了相当大份额;</li> <li>第二阶段从 1995 年到 2000 年,这一阶段由于标准模板库(STL)和后来的 Boost 等程式库的出现,泛型程式设计在 C++ 中占据了越来越多的比重性。当然,同时由于 Java、C#等语言的出现和硬件价格的大规模下降,C++受到了一定的冲击;</li> <li>第三阶段从 2000 年至今,由于以 Loki、MPL 等程式库为代表的产生式编程和模板元编程的出现,C++出现了发展历史上又一个新的高峰,这些新技术的出现以及和原有技术的融合,使 C++ 已经成为当今主流程式设计语言中最复杂的一员。</li> </ul> <p> 在《<a title="Why C++ ? 王者归来" href="/misc/goto?guid=4958328540779826906" target="_blank">Why C++? 王者归来</a>》中说了 ,性能主要就是要省电,省电就是省钱,在数据中心还不明显,在手机上就更明显了,这就是为什么 Android 支持 C++ 的原因。所以,在 NB 的电池或是能源出现之前,<strong>如果你需要注重程序的运行性能和开发效率,并更关注程序的运性能,那么,应该首选 C++</strong>。这就是 iOS 开发也支持 C++ 的原因。</p> <p style="text-align:center;"><img title="" alt="C++的坑真的多吗?" src="https://simg.open-open.com/show/f57fe2ae958625f3d5f7a1b882762af8.jpg" width="567" height="318" /></p> <p> 今天的C++11中不但有更多更不错的东西,而且,还填了更多原来 C++ 的坑。(参看:<a href="/misc/goto?guid=4958521770347173782" target="_blank">C++11 Wiki</a>,<a title="C++11 中值得关注的几大变化(详解)" href="/misc/goto?guid=4958521770441585277" target="_blank">C++ 11 的主要特性</a>)</p> <p style="text-align:center;"> <img title="" alt="C++的坑真的多吗?" src="https://simg.open-open.com/show/585c48ddb123d62af6a5f250a2cddd1e.jpg" width="567" height="319" /></p> <p> <strong><strong>总结</strong></strong></p> <ul> <li><strong>C++并不完美,但学 C++ 必然让你受益无穷。</strong></li> </ul> <ul> <li><strong>是那些不合格的、想对编程速成的程序员让 C++ 变得坑多。</strong></li> </ul> <p> 最后,非常感谢能和“@简悦云风”,“@淘宝诸霸”,“@Laruence”一起讨论这个问题!无论你们的观点怎么样,我都和你们“在一起”,嘿嘿嘿……</p> </div>