Posted on 2008-11-03 20:32
Batiliu 閱讀(306)
評論(0) 編輯 收藏 引用 所屬分類:
思考與感悟
劉未鵬(pongba) /文
C++的羅浮宮(http://blog.csdn.net/pongba)
問題
為什么用C++呢? 在你皺著眉頭離開之前,試著回答這個(gè)簡單的問題。效率,是么?人人都知道這個(gè)。但情況是,當(dāng)一個(gè)人開始討論編程語言或與其相關(guān)的話題時(shí),他必須要非常明確而有針對性。為什么呢?我來問你另一個(gè)問題:如果效率是人們使用C++的唯一理由,那么為啥不直接用C呢?C被認(rèn)為比C++效率更高(嗯嗯,我知道C沒有比C++的效率高多少,所以這里別誤解我的意思,因?yàn)榧词顾鼈兌咝氏嗤?,剛才的問題依然存在)。
迷思
我知道你又要說“更好的抽象機(jī)制”了,因?yàn)楫吘笴++是要設(shè)計(jì)成一個(gè)更好的C的。C++沒有犧牲效率,同時(shí)又添加了這么多高級特性。但問題是,“開發(fā)者們真的需要這些高級特性么?”。畢竟我們一直聽人講KISS(Keep It Simple and Stupid)之類的東西。我們也都聽到有聲稱C比C++更KISS所以我們要用C云云。這種持續(xù)不斷的爭論將C與C++之間的比較變成了一個(gè)大大的迷題(或者說是混亂)。令人驚訝的是,貌似的確有很多人更加傾向于用C,最大的理由就是C++實(shí)在是太難用對了。甚至Linus也這么想。
這種現(xiàn)象最大的影響就是當(dāng)人們在C和C++之間權(quán)衡時(shí),使人們傾向于使用C。而且一旦人們開始用C,他們很快就適應(yīng)并滿足了(其實(shí),在任何語言乃至任何人類活動(dòng)中都有此現(xiàn)象,C++亦然,比如常常聽到有人說“XX語言我用了這么多年,一直用得好好的”,照這種說法任何圖靈完備的語言還不都是能用來編程?)。于是即使他們還沒有試試C++,或者他們還沒成為好的C++程序員時(shí),他們就開始聲稱C比C++更好了。然而其實(shí)呢,真實(shí)的答案往往總是取決于實(shí)際情況的。
我說過“取決于實(shí)際情況”了么?那到底實(shí)際情況是什么呢?顯然,有些領(lǐng)域C是更好的選擇。例如設(shè)備驅(qū)動(dòng)開發(fā)就不需要那些OOP/GP技巧。而只是簡單的處理數(shù)據(jù),真正重要的是程序員確切地知道系統(tǒng)是如何運(yùn)轉(zhuǎn)的,以及他們正在做什么。那么寫操作系統(tǒng)呢?我本人并沒有參與任何操作系統(tǒng)的開發(fā),但我讀過不少操作系統(tǒng)代碼(大多是unix的)。我的感覺是操作系統(tǒng)很大一部分也不需要OOP/GP。
但是,這就表示在所有效率重要的領(lǐng)域,C都是比C++更好的選擇么?未必。
答案
讓我們一個(gè)一個(gè)來分析。
首先,當(dāng)人們關(guān)注效率時(shí),有2種效率——時(shí)間效率(例如OS,運(yùn)行時(shí)庫,實(shí)時(shí)應(yīng)用程序,high-demanding的系統(tǒng))和空間效率(例如各種嵌入式系統(tǒng))。但是,這樣的分類并不能幫我們決定用C還是C++,因?yàn)镃和C++的時(shí)空效率都很高。真正影響選擇語言的因素是業(yè)務(wù)邏輯(這里的“業(yè)務(wù)邏輯”并非表示“企業(yè)應(yīng)用業(yè)務(wù)”)。例如,使用OOP/GP來表達(dá)邏輯(或者說代碼的結(jié)構(gòu))好呢,還是就只用數(shù)據(jù)和過程好呢?
據(jù)此觀點(diǎn),我們可以把應(yīng)用程序大致分為兩類(當(dāng)然前提是關(guān)注的是C/C++而不是java/C#/ruby/erlang等等):底層應(yīng)用程序和高層應(yīng)用程序。這里底層是指像OB/OO和GP沒啥用處的地方,其余歸到高層。顯然,在所有C/C++應(yīng)用的領(lǐng)域(這些領(lǐng)域需要C/C++的效率),屬于高層的應(yīng)用有很多(可以看看Bjarne Stroustrup在他主頁上的列表)。在這些領(lǐng)域中,抽象至少是和效率一樣重要的。而這些正是C++適用的場合。
等等還有。即使在程序員不需要高級抽象的領(lǐng)域,也不是就絕對用不到C++的。為啥呢?僅僅是因?yàn)槟愕拇a中沒有用類或模板并不意味著不能用以類或模板實(shí)現(xiàn)的庫。因?yàn)橛腥绱吮姸喾奖愕腃++庫(還有即將到來的tr1/tr2),我覺得有充分的理由在這些領(lǐng)域中使用C++——你可以在編碼時(shí)僅使用C++中的C核心(以任何你喜歡的方式來KISS),同時(shí)還能用強(qiáng)大的C++庫(比如STL容器、算法和tr1/tr2的組件)。
最后,我認(rèn)為人們還常常忽略了一點(diǎn)——有時(shí)KISS也是建立在抽象上的。我覺得Matthew Wilson在他新書《Extended STL,卷1》的序言中對此做了很好的闡釋。他寫了2段代碼,一段用C,另一段用C++:
C:
DIR* dir = opendir(".");
if(NULL != dir)
{
struct dirent* de;
for(; NULL != (de = readdir(dir)); )
{
struct stat st;
if( 0 == stat(de->d_name, &st) &&
S_IFREG == (st.st_mode & S_IFMT))
{
remove(de->d_name);
}
}
closedir(dir);
}
C++:
readdir_sequence entries(".", readdir_sequence::files);
std::for_each(entries.begin(), entries.end(), ::remove);
而在C++09里面更簡單:
std::for_each(readdir_sequence(".", readdir_sequence::files), ::remove);
也就是說,我認(rèn)為即使一個(gè)人在自己的代碼里不需要類或模版,他也有理由用C++,因?yàn)?/strong>他用的那些方便的C++庫用到了類和模板。如果一個(gè)高效的容器(或智能指針)能把你從無聊的手動(dòng)內(nèi)存管理中解放出來,為啥還要用那原始的malloc/free呢?如果一個(gè)更好的string類(我可沒說std::string,地球人都知道那個(gè)不是C++中能做出的最好的string類)或正則表達(dá)式類能把你從一坨一坨的、你看都不想看的處理字符串的代碼中解脫出來,那么為啥還要手動(dòng)去做這些事呢?如果一個(gè) "transform"(或"for_each")能夠用一行代碼把事情漂亮搞定,為啥還要手寫一個(gè)for循環(huán)呢?如果高階函數(shù)能滿足你的需要,那么為啥還要用笨拙的替代方法呢?(OK,我知道,最后兩個(gè)需要C++加入lambda支持才真正擺脫雞肋的罵名——這正是C++0x的任務(wù)嘛)
總之,我認(rèn)為KISS并不等同于“原始”;KISS意味著用最適合的工具來做事情,這里“最合適”的意思是工具能夠幫你以盡量直接簡潔的方式來表達(dá)思想,同時(shí)又不降低代碼的可讀性,另外還保持代碼容易理解。總之,我認(rèn)為KISS并不等同于“原始”;KISS意味著用最適合的工具來做事情,這里“最合適”的意思是工具能夠幫你以盡量直接簡潔的方式來表達(dá)思想,同時(shí)又不降低代碼的可讀性,另外還保持代碼容易理解。
真正的問題
人們可能會說,相較于被正確使用而言,C++(遠(yuǎn)遠(yuǎn))更容易被錯(cuò)誤使用。而相比而言,C程序的復(fù)雜性更容易管理和控制。在C++中,一個(gè)普通程序員很可能會寫出一堆高度耦合的類,很快情況就變得一團(tuán)糟。但這個(gè)其實(shí)是另外一個(gè)問題。在另一方面,這種事情也很可能發(fā)生在任何一門面向?qū)ο笳Z言中,因?yàn)榭偸怯谐绦騿T在還沒弄懂什么是HAS-A和IS-A之前,就敢于在類上再寫類,疊床架屋的一層一層摞上去。他們學(xué)會了在一門特定的語言中如何定義類,如何繼承類的語法,然后他們就認(rèn)為自己已經(jīng)掌握了OOP的精髓了。另一方面,這一問題在C++中更為嚴(yán)重,因?yàn)镃++有如此眾多的偶然復(fù)雜性在阻礙設(shè)計(jì);而且C++又是如此靈活,很多問題在C++中都有好幾種解決辦法(想想那么多的GUI庫吧),于是在這些選擇中進(jìn)行權(quán)衡本身就成了一個(gè)困難。C++中的非本質(zhì)復(fù)雜性是其歷史包袱使然,而C++0x正是要努力消除這些非本質(zhì)復(fù)雜性(在這方面C++0x的工作的確做得很不錯(cuò))。對于設(shè)計(jì)來說,靈活性不是個(gè)壞事情——可以幫助好的設(shè)計(jì)者作出好的設(shè)計(jì)。如果有人抱怨說這個(gè)太費(fèi)腦細(xì)胞了,那可能是這個(gè)設(shè)計(jì)者本身的問題,而不能怪語言。可能就不該讓他來作設(shè)計(jì)。如果你擔(dān)心C++的高級特性會把你的同事引入歧途,把項(xiàng)目搞砸,那你也許應(yīng)該制定一份編碼標(biāo)準(zhǔn)并嚴(yán)格推行(或者你也可以遵循C++社群這些年積攢下來的智慧,或者在必要時(shí),只使用C++中的C或C with class那部分),而不是因?yàn)橛酗L(fēng)險(xiǎn)就躲開C++(其實(shí)這些風(fēng)險(xiǎn)可以通過一些政策來避免的),因?yàn)槟菢拥脑?,你就沒法用那些C++的庫了。
另一方面,其實(shí)一個(gè)更為重要的問題是一個(gè)心理學(xué)問題——如果一門語言中存在某個(gè)奇異的特性或旮旯,那么遲早總會有人發(fā)現(xiàn)的,總會有人為之吸引的,然后就使人們從真正有用的事情中分心出來(這有點(diǎn)像Murphy法則),更不用說那些有可能對真正問題帶來(在某種程度上)漂亮的解決方案的語言旮旯了。人們本性上就容易受到稀有資源的誘惑。奇技淫巧是稀有資源,于是奇技淫巧便容易吸引人們的注意力,更別說掌握一個(gè)技巧還能夠讓那人在他那圈子里感覺非常牛了。退一萬步,你會發(fā)現(xiàn),即使是一個(gè)廢柴技巧也能引起人們足夠的興趣來。
C++中有多少陰暗角落呢?C++中又有多少技巧呢?總的來說,C++中,有多少非本質(zhì)復(fù)雜性呢?(懂一定C++的人一定知道我在說什么)
平心而論,近年來(現(xiàn)代C++中)發(fā)現(xiàn)的大多數(shù)技巧或(如果你愿意稱之為)技術(shù)實(shí)際上都是由實(shí)際需求驅(qū)動(dòng)的,尤其是需要實(shí)現(xiàn)高度靈活而又普遍適用(generic)的類庫 (例如boost中的那些玩意)。而這些技巧也的確(在某種程度上)提供了對實(shí)際問題的漂亮解決方案。讓我們來這么想一下,如果你處于一個(gè)兩難境地:要么用那些奇技淫巧來做點(diǎn)很有用的東西,要么不做這樣其他人也就沒得用。你會如何選擇呢?我知道boost的英雄們選擇了前者——不管多么困難多么變態(tài)多么齷齪,把它做出來!
但所有這些爭論都不能改變一個(gè)事實(shí):我們理應(yīng)享有一個(gè)語言,能夠讓我們用代碼清晰的表達(dá)思想。以boost.function/boost.bind/boost.tuple為例,variadic templates可以大大簡化這幾個(gè)庫的實(shí)現(xiàn)(減至幾乎是原先1/10的代碼行數(shù)),同時(shí)代碼也(遠(yuǎn)遠(yuǎn))更加簡潔易懂。Auto,initializer-list,rvalue-reference,template-aliasing,strong-typed enums,delegating-constructors,constexpr,alignments,inheriting-constructors,等等等等,所有這些C++0x的特性,都有一個(gè)共同目的——消除語言中多方面的非本質(zhì)復(fù)雜性或語言中的尷尬之處。
正如Bjarne Stroustrup所說,很顯然C++太過復(fù)雜了,很顯然人們被嚇壞了,并且時(shí)不時(shí)就不用C++了。但“人們需要相對復(fù)雜的語言去解決絕對復(fù)雜的問 題”。我們不能通過減少語言特性而使其更加強(qiáng)大。復(fù)雜的特性就連模板甚至多繼承這樣的也是有用的——如果你正好需要它們,而且如果你極其小心使用,不要搬起石頭砸自己的腳的話。其實(shí)在所有C++的復(fù)雜性當(dāng)中,真正阻礙了我們的是“非本質(zhì)復(fù)雜性”(有人稱之為“尷尬之處”),而不是語言所支持的編程范式(其實(shí)也就3個(gè)而已)。而這也正是我們應(yīng)該擁抱C++0x的重要原因,因?yàn)镃++0x正是要消除那些長期存在的非本質(zhì)復(fù)雜性,同時(shí)也使得那些奇技淫巧不再必要(很顯然,目前這些技巧堆積如山,翻翻那些個(gè)C++的書籍,或者瞅瞅boost庫,你就知道我在說啥了),這樣我們就能夠直觀清晰的表達(dá)思想。
結(jié)論
C++難用,更難用對。所以當(dāng)你決定用它時(shí),要小心,要時(shí)刻牢記自己的需求,所要達(dá)到的目的。這里有一個(gè)簡單的指南:
我們需要高效率么?
如果需要,那么
我們需要抽象么(請仔細(xì)思考這一點(diǎn),因?yàn)?em>很難評估使用C++高級特性是否能夠抵消誤用這些機(jī)制的風(fēng)險(xiǎn);正確的回答取決于程序員的水平有多高,遵循哪種編碼標(biāo)準(zhǔn)以及編碼標(biāo)準(zhǔn)執(zhí)行得如何,等等)?
如果是,那么
用C++吧。
如果不是,那么,
我們需要用C++庫來簡化開發(fā)么?
如果是,那么
就用C++吧。但同時(shí)必須時(shí)刻牢記你在做什么——如果你的代碼不需要那些“漂亮的”抽象,那就別試圖使用以免陷入其中。別只是因?yàn)槟阍?/strong>.cpp文件中寫代碼以及你用的是C++編譯器就要用類啊、模板啊這些東西。
如果不是,那
就用C。不過你又會想為啥不僅僅使用C++中屬于C的那部分核心呢?還是老原因:人們很容易就陷入到語言的“漂亮”特性中去了,即使他們還不知道這些特性是否有用。我都記不清有多少次自己寫了一大堆的類和繼承,到最后反倒要問自己“要這么些個(gè)類和繼承做什么呀?”。所以,如果你能堅(jiān)持只用C++中C或C with class的那部分,并遵循“讓簡單的事情保持簡單”的理念;或者你需要把C代碼遷移到C++中來的話,那么就用C++吧,但要十分小心。另一方面,如果你既不需要抽象機(jī)制,也不需要C++庫,因?yàn)槭虑榉浅:唵?/strong>,不需要方便的組件例如容器和字符串,或者你已認(rèn)定C++能夠給項(xiàng)目帶來的好處微乎其微,不值得為之冒風(fēng)險(xiǎn),或者干脆就沒那么多人能用好C++,那么可能你還是只用C的好。
底線是:讓簡單的事情保持簡單(但同時(shí)也請記?。汉唵涡钥梢酝ㄟ^使用高級庫來獲得);必要時(shí)才使用抽象(切記不可濫用;遵循好的設(shè)計(jì)方法和最佳實(shí)踐)。