神話與謬誤:爭(zhēng)論C++前你應(yīng)當(dāng)知道什么

字號(hào):

哈雷將軍的笑話想必大家都聽(tīng)過(guò)。一句話經(jīng)口口相傳,每個(gè)人都根據(jù)自己的主觀意念加以潤(rùn)色,修補(bǔ),歪曲…到最后就面目全非。這里最關(guān)鍵的一環(huán)就是主觀意識(shí),在歷史學(xué)里面有這么一句話,大致意思是歷史其實(shí)只存在于人的意念之中;就算完全客觀的事件,通過(guò)不同的人的嘴說(shuō)出來(lái),造成的心理效應(yīng)也往往不一樣,每個(gè)人都會(huì)加上那么一兩個(gè)形容詞,駕馭語(yǔ)言能力高的更是能夠舌綻蓮花,而語(yǔ)言本就有自身的力量,其中的遣詞造句對(duì)讀者構(gòu)成的心理影響力便應(yīng)運(yùn)而生。甚至于同一句話,用不同的語(yǔ)氣說(shuō)出來(lái),都會(huì)造成不同的效果。同一句話,站在不同的立場(chǎng)上看,也會(huì)根本不是同一個(gè)意思。比如“C++還算是門(mén)不錯(cuò)的語(yǔ)言”,站在C++擁護(hù)者的角度聽(tīng)是在憐憫加詆毀C++,而站在C++反對(duì)者的角度聽(tīng)卻是抬舉了C++。
    在一個(gè)長(zhǎng)期被廣泛爭(zhēng)論的話題中,幾乎無(wú)可避免的總是存在一些Fallacies和Myths。比如動(dòng)態(tài)&靜態(tài)類(lèi)型系統(tǒng)的爭(zhēng)論,據(jù)說(shuō)從圖靈時(shí)代就開(kāi)始了,到現(xiàn)在還有各種各樣的誤解,而且,可以說(shuō),時(shí)間越長(zhǎng),系統(tǒng)內(nèi)的Fallacy越多。就連異常(exception)這樣不算復(fù)雜的語(yǔ)言特性里面居然也有一些長(zhǎng)期存在的誤解。
    至于這些Fallacies和Myths出現(xiàn)的原因很多:有人要“內(nèi)涵”唬人、有人要維護(hù)自己的心理優(yōu)勢(shì)、有人要維護(hù)自己的政權(quán)、有人要維護(hù)自己的利益、有人因?yàn)樵拸膭e人那里聽(tīng)了半句轉(zhuǎn)述給別人聽(tīng)的時(shí)候按主觀意念補(bǔ)全(誰(shuí)愿意說(shuō)“我不知道”呢?)、有人干脆就是人云亦云… 所以,一句話,在一個(gè)靠口頭表達(dá)交換信息的社會(huì)中,F(xiàn)allacies和Myths是無(wú)處不在的,因?yàn)閺膬?nèi)心真實(shí)想法到外界表現(xiàn)出來(lái)的想法之間存在著“口頭表達(dá)”這一中間層,后者由主觀意志支配。這里的中間層可不比軟件工程里面的間接層,在這個(gè)間接層上惡魔可以變成天使,天使也可以變成惡魔;六月飛雪可以變成天降祥瑞,瓢潑大雨也可以變成艷陽(yáng)高照。Anyway,這展開(kāi)來(lái)就是一個(gè)心理學(xué)的問(wèn)題了,不多廢話了,有興趣的可以去看Harry G. Frankfurt寫(xiě)的《On Bullshit》或者Scott Berkun的這篇短文——“How to detect bullshit”。呃…我說(shuō)“一句話”了么?
    C++ - Fallacies and Myths
    C++作為一門(mén)被爭(zhēng)論不斷的語(yǔ)言,其中Fallacies和Myths自然不會(huì)少。一般來(lái)說(shuō),一個(gè)問(wèn)題在被大眾爭(zhēng)論中交換的話語(yǔ)數(shù)量與其中的Fallacy數(shù)量成正比。但一般來(lái)說(shuō)主要的Fallacies就那么幾個(gè):
    Fallacy #1 ——C++社群的哲學(xué)太學(xué)院派
    讓我們先對(duì)“學(xué)院派”下一個(gè)定義好不好?先問(wèn)你自己一個(gè)問(wèn)題,你心目中對(duì)“學(xué)院派”的定義是什么? 以下是一些選項(xiàng): 1. 傾向于理論美。2. 忽視實(shí)際編碼中的constraints(如效率,模塊性、可讀性等等)。3. 倡#計(jì)算機(jī)等級(jí)考試# #神話與謬誤:爭(zhēng)論C++前你應(yīng)當(dāng)知道什么#言律師行為。4. 鉆細(xì)節(jié)。5. … 我想如果我說(shuō)C++語(yǔ)言設(shè)計(jì)強(qiáng)調(diào)理論美,所有學(xué)過(guò)C++的人恐怕都會(huì)笑了…正如Bjarne自己所說(shuō)的,C++設(shè)計(jì)初期的Rule of Thumb之一便是“不要陷入到對(duì)完美性的固執(zhí)追求中”;不過(guò)具有諷刺意味的是,后面你會(huì)看到,正是這樣的一種哲學(xué)帶來(lái)了今天對(duì)C++的這個(gè)誤解。
    我猜持這樣一種觀點(diǎn)的人大多對(duì)于學(xué)院派的定義都是模糊的,一般都介于“提倡鉆語(yǔ)言細(xì)節(jié)并利用語(yǔ)言細(xì)節(jié)的做法”、“關(guān)注語(yǔ)言特性本身而忽略實(shí)際編碼需求”、“對(duì)語(yǔ)言細(xì)節(jié)無(wú)休止的爭(zhēng)論”等等之間。 所以,當(dāng)有人說(shuō)“C++==學(xué)院派”的時(shí)候,他的真實(shí)意思很可能是:“C++語(yǔ)言的陰暗角落太多,而且C++社群還有提倡對(duì)語(yǔ)言角落把握的潛在哲學(xué),就連C++0x的進(jìn)化也似乎更多關(guān)注語(yǔ)言特性,而那些語(yǔ)言特性根本就跟我們實(shí)際開(kāi)發(fā)者脫節(jié)了…”等等。 首先得承認(rèn)的是,在近一個(gè)十年的時(shí)間內(nèi),C++社群的確某種程度上建立起了一種對(duì)語(yǔ)言細(xì)節(jié)過(guò)分關(guān)注的心態(tài),這種心態(tài)毫無(wú)疑問(wèn)是錯(cuò)誤的,但只有知道這個(gè)錯(cuò)誤是如何來(lái)的,才能解開(kāi)這個(gè)結(jié)。而且,就算一時(shí)解不開(kāi)這個(gè)結(jié),知道了原因之后才能保持理性的寬容態(tài)度,而不是亂發(fā)抱怨。一個(gè)理性的態(tài)度,更有助于良性發(fā)展。例如如果C++社群都能明白這種潛哲學(xué)從何而來(lái),或許也就會(huì)漸漸走向更好的發(fā)展了。
    我用一個(gè)例子來(lái)說(shuō)明這一點(diǎn):你平時(shí)遍歷一個(gè)數(shù)組,或一個(gè)容器的時(shí)候是怎么做的?
    for(std::vector::iterator it = v.begin(); it != v.end(); ++it) {…} 這種做法很臃腫。其實(shí)你的邏輯是“對(duì)v中的每個(gè)元素,做…事情”,你知道大多數(shù)其它流行的語(yǔ)言中都有內(nèi)建的for_each。那C++中就沒(méi)有了嗎?有。STL的for_each算法,于是你寫(xiě): struct MyOp{void operator()(int& i){…}}; std::for_each(v.begin(), v.end(), MyOp()); 這個(gè)方案實(shí)際很差。一是你還是得寫(xiě)v.begin()、v.end(),二是你得為此定義一整個(gè)新類(lèi)。三是這個(gè)新類(lèi)并不在你使用這個(gè)新類(lèi)(for_each被調(diào)用)的點(diǎn)上,因?yàn)榫植款?lèi)不能做模板參數(shù)。 你要的是lambda function: for_each(v.begin(), v.end(), <>(int& i){ …}); 可是C++98沒(méi)有。 你要的是內(nèi)建foreach: for(int& i : v) {…} 可是C++98沒(méi)有。
    鑒于循環(huán)結(jié)構(gòu)是編程中最常出現(xiàn)的結(jié)構(gòu)之一。這個(gè)問(wèn)題其實(shí)還是比較惱人的,如果你覺(jué)得不惱人可能只是因?yàn)槟氵m應(yīng)性習(xí)慣了,這未必是好事。比如每次都要寫(xiě)std::vector::iterator就很讓人惱火,如果我換個(gè)容器,就要修改一堆std::vector<…>。那用typedef行不行啊?行??扇匀贿€是需要寫(xiě)一次typedef,我很懶,我什么多余的無(wú)用代碼都不想寫(xiě)。要知道,每多出一行無(wú)用的(并非因表達(dá)思想所需要才出現(xiàn))的代碼,就增加一點(diǎn)維護(hù)負(fù)擔(dān),這也正是為什么語(yǔ)言的表達(dá)力如此重要的原因。
    那怎么辦?如果我告訴你,C++98里面其實(shí)你也可以寫(xiě): foreach(int& i , v){ …} 你怎么想? 廢話。當(dāng)然是求之不得了。有這么簡(jiǎn)潔的表達(dá)方式誰(shuí)還不想用啊。 我需要告訴你的另一個(gè)事實(shí)是。為了在C++98里面幾近完美地實(shí)現(xiàn)這個(gè)特性,有人把標(biāo)準(zhǔn)的角落挖了個(gè)底朝天。不,我不是在為鉆語(yǔ)言細(xì)節(jié)找理由,我只是想告訴你,許多人所認(rèn)為的鉆語(yǔ)言細(xì)節(jié)的做法,其實(shí)一開(kāi)始大多是由用戶實(shí)際需求驅(qū)動(dòng)的,這個(gè)foreach設(shè)施被C++程序員們?cè)噲D實(shí)現(xiàn)了N遍N種做法,可見(jiàn)需求之強(qiáng)烈??上Ы^大多數(shù)實(shí)現(xiàn)都遠(yuǎn)遠(yuǎn)稱不上好用,就連現(xiàn)在這個(gè)實(shí)現(xiàn)的作者也早在03年在CUJ上發(fā)了一個(gè)實(shí)現(xiàn),也稱不上好用。是后來(lái)又契而不舍才實(shí)現(xiàn)了最終這個(gè)真正好用的版本的。 我想說(shuō)的是,上面這個(gè)美好的foreach,當(dāng)然人人都想用。但問(wèn)題是要在C++98下實(shí)現(xiàn)它只能靠挖標(biāo)準(zhǔn),這是的途徑。要不然就得等語(yǔ)言進(jìn)化,并忍受若干年,誰(shuí)愿意?況且這個(gè)foreach設(shè)施還能作占位符,在C++09來(lái)臨之前兢兢業(yè)業(yè)履行其職責(zé),C++09加入內(nèi)建foreach支持之后只消用正則表達(dá)式搜索全局替換,就OK了,沒(méi)有任何的升級(jí)麻煩。
    再舉一個(gè)經(jīng)典的例子:STL里面的traits。其實(shí)traits不應(yīng)該是traits。traits最自然的實(shí)現(xiàn)方式應(yīng)該是C++09的concept。但STL需要用到靜態(tài)dispatch技術(shù)啊,那怎么辦?要么用traits(增加語(yǔ)言復(fù)雜性),要么不用(顯然不行)。
    再舉個(gè)經(jīng)典的例子:模板元編程。模板元編程有啥用?日常開(kāi)發(fā)者八輩子估計(jì)也用不到。但真的嗎?沒(méi)錯(cuò),日常開(kāi)發(fā)者并不會(huì)直接用到。但是,由模板元編程支持的各個(gè)boost子庫(kù)呢?被選入C++0x的TR1的各個(gè)子庫(kù)呢(間接用到)?那日常開(kāi)發(fā)者用不用學(xué)模板元編程呢?不用學(xué),根本不用學(xué),這么復(fù)雜的技術(shù)學(xué)什么呢?也就是點(diǎn)技巧上的東西。那為什么偏有人學(xué)呢?待會(huì)再說(shuō)。 還有大量的例子就不一一列了。其實(shí)STL的traits技術(shù)已經(jīng)能夠說(shuō)明問(wèn)題了。如果你仔細(xì)看一看,你會(huì)發(fā)現(xiàn),那些所謂的利用C++黑暗角落的技術(shù),幾乎無(wú)一不是出現(xiàn)在庫(kù)開(kāi)發(fā)里面的,而之所以出現(xiàn)在庫(kù)開(kāi)發(fā)里面,是因?yàn)閹?kù)開(kāi)發(fā)中的需求驅(qū)動(dòng)的——為了開(kāi)發(fā)出更好的庫(kù)。難道你不想用更好的庫(kù)? 哦,說(shuō)到“更好的庫(kù)”,肯定會(huì)有同學(xué)有意見(jiàn)了。
    C++98都快十年了,標(biāo)準(zhǔn)庫(kù)還是只有那一套STL。庫(kù)進(jìn)展緩慢,到現(xiàn)在GUI庫(kù)也沒(méi)有一個(gè)標(biāo)準(zhǔn),都是四分五裂各自為營(yíng)。網(wǎng)絡(luò)庫(kù)也是、文件系統(tǒng)庫(kù)也是、日志庫(kù)也是…不過(guò)這個(gè)問(wèn)題已經(jīng)是另一個(gè)問(wèn)題了,容后再說(shuō)。
    問(wèn)題是,“沒(méi)有標(biāo)準(zhǔn)的庫(kù)”并不意味著“C++的庫(kù)不好”,后者也并不意味著“那些晦澀的技巧并沒(méi)有提升庫(kù)的質(zhì)量”,這個(gè)邏輯上的兩環(huán)都不對(duì)。實(shí)際上,人們所謂的“晦澀而復(fù)雜的技巧”其實(shí)正是為了提升庫(kù)的質(zhì)量而被挖掘出來(lái)的。traits技術(shù)提升庫(kù)的效率(靜態(tài)轉(zhuǎn)發(fā)),type erase技術(shù)使得boost::function可以接受任何簽名為void()的函數(shù)(靈活性),包括仿函數(shù),包括boost::bind后的函數(shù)。type list技術(shù)使得boost::tuple能夠接受可變數(shù)目的模板參數(shù)。policy-based design使得可以對(duì)一個(gè)設(shè)施的功能進(jìn)行正交分解… 就算把所有流行的C++ tricks都列出來(lái),你也會(huì)發(fā)現(xiàn),其實(shí)它們幾乎每一個(gè)都對(duì)應(yīng)了至少一個(gè)實(shí)際應(yīng)用。而實(shí)際應(yīng)用需求哪來(lái)的?庫(kù)設(shè)計(jì)的需求。但歸根到底,是使用庫(kù)的人——終端程序員——的需求。(效率、靈活性、抽象表達(dá)力,哪一樣不是終端程序員的實(shí)際需求呢?)