來源:陳皓
先說明一下,我不希望本文變成語言爭論貼。希望下面的文章能讓我們客觀理性地瞭解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++程式設計師淡定。
不要太糾結上圖,只是輕鬆一下,我沒那麼無聊,讓我們來看點真正的論據。
相信用過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語言程式設計和維護的坑。少用指標,多用引用,試試autoptr,用用封裝,繼承,多型和函式過載…… 你面對的坑只會比C少,不會多。
C++的坑有多少?
C++的坑真的不多,如果你能花兩到三週的時候讀一下《Effective 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的《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 Wiki,C++ 11的主要特性)
總結
●C++並不完美,但學C++必然讓你受益無窮。
●是那些不合格的、想對程式設計速成的程式設計師讓C++變得坑多。
最後,非常感謝能和“@簡悅雲風”,“@淘寶諸霸”,“@Laruence”一起討論這個問題!無論你們的觀點怎麼樣,我都和你們“在一起”,嘿嘿嘿……
(全文完)