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