從CSDN上讀到一篇文章,深有同感,原文如下:
傳統(tǒng)上認(rèn)為,C++相對(duì)于目前一些新潮的語(yǔ)言,如Java、C#,優(yōu)勢(shì)在于程序的運(yùn)行性能。這種觀(guān)念并不完全。如果一個(gè)人深信這一點(diǎn),那么說(shuō)明他并沒(méi)有充分了解和理解C++和那個(gè)某某語(yǔ)言。同時(shí),持有這種觀(guān)念的人,通常也是受到了某種誤導(dǎo)(罪魁禍?zhǔn)桩?dāng)然就是那些財(cái)大氣粗的公司)。對(duì)于這些公司而言,他們隱藏了C++同某某語(yǔ)言間的核心差別,而把現(xiàn)在多數(shù)程序員不太關(guān)心的差別,也就是性能,加以強(qiáng)化。因?yàn)殡S著cpu性能的快速提升,性能問(wèn)題已不為人們所關(guān)心。這叫“李代桃僵”。很多涉世不深的程序員,也就相信了他們。于是,大公司們的陰謀也就得逞了。
這個(gè)文章系列里,我將竭盡所能,利用一些現(xiàn)實(shí)的案例,來(lái)戳破這種謊言,還世道一個(gè)清白。但愿我的努力不會(huì)白費(fèi)。
軟件工程
一般認(rèn)為,使用Java或C#的開(kāi)發(fā)成本比C++低。但是,如果你能夠充分分析C++和這些語(yǔ)言的差別,會(huì)發(fā)現(xiàn)這句話(huà)的成立是有條件的。這個(gè)條件就是:軟件規(guī)模和復(fù)雜度都比較小。如果不超過(guò)3萬(wàn)行有效代碼(不包括生成器產(chǎn)生的代碼),這句話(huà)基本上還能成立。否則,隨著代碼量和復(fù)雜度的增加,C++的優(yōu)勢(shì)將會(huì)越來(lái)越明顯。
造成這種差別的就是C++的軟件工程性。在Java和C#大談軟件工程的時(shí)候,C++實(shí)際上已經(jīng)悄悄地將軟件工程性提升到一個(gè)前所未有的高度。這一點(diǎn)被多數(shù)人忽視,并且被大公司竭力掩蓋。
語(yǔ)言在軟件工程上的好壞,依賴(lài)于語(yǔ)言的抽象能力。從面向過(guò)程到面向?qū)ο螅Z(yǔ)言的抽象能力有了一個(gè)質(zhì)的飛躍。但在實(shí)踐中,人們發(fā)現(xiàn)面向?qū)ο鬅o(wú)法解決所有軟件工程中的問(wèn)題。于是,精英們逐步引入、并拓展泛型編程,解決更高層次的軟件工程問(wèn)題。(實(shí)際上,面向?qū)ο蠛头盒途幊痰钠鹪炊伎梢宰匪莸?967年,但由于泛型編程更抽象,所以應(yīng)用遠(yuǎn)遠(yuǎn)落后于面向?qū)ο螅?br> 一個(gè)偶然的機(jī)會(huì),我突發(fā)奇想,試圖將貨幣強(qiáng)類(lèi)型化,使得貨幣類(lèi)型可以采用普通的算術(shù)表達(dá)式計(jì)算,而無(wú)需關(guān)心匯率換算的問(wèn)題。具體的內(nèi)容我已經(jīng)寫(xiě)成文章,放在blog里:http://blog.csdn.net/longshanks/archive/2007/05/30/1631391.aspx。(CSDN的論壇似乎對(duì)大文章有些消化不良)。下面我只是簡(jiǎn)單地描述一下問(wèn)題,重點(diǎn)還在探討語(yǔ)言能力間的差異。
當(dāng)時(shí)我面臨的問(wèn)題是:假設(shè)有四種貨幣:RMB、USD、UKP、JPD。我希望能夠這樣計(jì)算他們:
RMB rmb_(1000);
USD usd_;
UKP ukp_;
JPD jpd_(2000);
usd_=rmb_;//賦值操作,隱含了匯率轉(zhuǎn)換。usd_實(shí)際值應(yīng)該是1000/7.68=130.21
rmb_=rmb_*2.5;//單價(jià)乘上數(shù)量。
ukp_=usd_*3.7;//單價(jià)乘上數(shù)量,賦值給英鎊。隱含匯率轉(zhuǎn)換。
double n=jpd_/(usd_-ukp_);//利用差價(jià)計(jì)算數(shù)量。三種貨幣參與,隱含匯率轉(zhuǎn)換。
而傳統(tǒng)上,我們通常用一個(gè)double或者currency類(lèi)型表示所有貨幣。于是,當(dāng)不同幣種參與運(yùn)算時(shí),必須進(jìn)行顯式的匯率轉(zhuǎn)換:
double rmb_(100), usd_(0), ukp_(0), jpn_(2000);
usd_=rmb_*usd_rmb_rate;
ukp_=(usd_*usd_ukp_rate)*3.7;
double n=jpd_/((usd_*usd_jpd_rate)-(ukp_*ukp_jpd_rate))
很顯然,強(qiáng)類(lèi)型化后,代碼簡(jiǎn)潔的多。并且可以利用重載或特化,直接給出與貨幣相關(guān)的輔助信息,如貨幣符號(hào)等(這點(diǎn)我沒(méi)有做,但加上也不復(fù)雜)。
在C++中,我利用模板、操作符重載,以及操作符函數(shù)模板等技術(shù),很快開(kāi)發(fā)出這個(gè)貨幣體系:
template<int CurrType>
class Currency
{
public:
Currency<CurrType>& operator=(count Currency<ct2>& v) {
…
}
public:
double _val;
…
};
template<int ty, int tp>
inline bool operator==(currency<ty>& c1, const currency<tp>& c2) {
…
}
template<int ty, int tp>
inline currency<ty>& operator+=(currency<ty>& c1, const currency<tp>& c2) {
…
}
template<int ty, int tp>
inline currency<ty> operator+(currency<ty>& c1, const currency<tp>& c2) {
…
}
…
總共不超過(guò)200行代碼。(當(dāng)然,一個(gè)工業(yè)強(qiáng)度的貨幣體系,需要更多的輔助類(lèi)、函數(shù)等等。但基本上不會(huì)超過(guò)500行代碼)。如果我需要一種貨幣,就先為其指定一個(gè)int類(lèi)型的常量值,然后typedef一下即可:
const int CT_RMB=0;//也可以用enum
typedef Currency<CT_RMB>RMB;
const int CT_USD=1;
typedef Currency<CT_USD>USD;
const int CT_UKP=2;
typedef Currency<CT_USD>USD;
const int CT_JPD=3;
typedef Currency<CT_USD>USD;
…
每新增一種貨幣,只需定義一個(gè)值,然后typedef即可。而對(duì)于核心的Currency<>和操作符重載,無(wú)需做丁點(diǎn)改動(dòng)。
之后,我試圖將這個(gè)貨幣體系的代碼移植到C#中去。根據(jù)試驗(yàn)的結(jié)果,我也寫(xiě)了一篇文章(也放在blog里:http://blog.csdn.net/longshanks/archive/2007/05/30/1631476.aspx)。我和一個(gè)同事(他是使用C#開(kāi)發(fā)的,對(duì)其更熟悉),用了大半個(gè)上午,終于完成了這項(xiàng)工作。
令人喪氣的事,上來(lái)就碰了個(gè)釘子:C#不支持=的重載。于是只能用asign<>()泛型函數(shù)代替。之后,由于C#的泛型不支持非類(lèi)型泛型參數(shù),即上面C++代碼中的int CurrType模板參數(shù)的泛型對(duì)等物,以及C#不支持泛型操作符重載,整個(gè)貨幣系統(tǒng)從泛型編程模式退化成了面向?qū)ο竽J健.?dāng)然,在我們堅(jiān)持不懈的努力下,最后終于實(shí)現(xiàn)了和C++中一樣的代碼效果(除了那個(gè)賦值操作):
assign(rmb_, ukp_);
assign(usd_, rmb_*3.7);
…
我知道,有些人會(huì)說(shuō),既然OOP可以做到,何必用GP呢?GP太復(fù)雜了。這里,我已經(jīng)為這些人準(zhǔn)備了一組統(tǒng)計(jì)數(shù)據(jù):在C#代碼中,我實(shí)現(xiàn)了3個(gè)貨幣,結(jié)果定義了4個(gè)類(lèi)(一個(gè)基類(lèi),三個(gè)貨幣類(lèi));重載30個(gè)算術(shù)操作符(和C++一樣,實(shí)現(xiàn)10個(gè)操作符,每個(gè)類(lèi)都得把10個(gè)操作符重載一遍);6個(gè)類(lèi)型轉(zhuǎn)換操作符(從兩種貨幣類(lèi)到第三貨幣類(lèi)的轉(zhuǎn)換操作符)。
這還不是最糟的。當(dāng)我增加一個(gè)貨幣,貨幣數(shù)變成4個(gè)后,數(shù)據(jù)變成了:5個(gè)類(lèi);40個(gè)算術(shù)操作符重載;12個(gè)類(lèi)型轉(zhuǎn)換操作符重載。
當(dāng)貨幣數(shù)增加到10個(gè)后:11個(gè)類(lèi);100個(gè)算術(shù)操作符重載;90個(gè)類(lèi)型轉(zhuǎn)換操作符重載。
反觀(guān)C++的實(shí)現(xiàn),3個(gè)貨幣時(shí):1個(gè)類(lèi)模板;1個(gè)賦值操作符重載模板;10個(gè)算術(shù)操作符重載模板;外加3個(gè)const int定義,3個(gè)typedef。
10個(gè)貨幣時(shí):1個(gè)類(lèi)模板;1個(gè)賦值操作符重載模板;10個(gè)算術(shù)操作符重載模板;const int定義和typedef分別增加到10個(gè)。
也就是說(shuō)C++版本的代碼隨著貨幣的增加,僅線(xiàn)性增加。而且代碼行增加的系數(shù)僅是2。請(qǐng)注意,是代碼行!不是類(lèi)、函數(shù),也不是操作符的數(shù)量。而C#版本的代碼量則會(huì)以幾何級(jí)數(shù)增加。幾何級(jí)數(shù)!!!
這些數(shù)字的含義,我就不用多說(shuō)了吧。無(wú)論是代碼的數(shù)量、可維護(hù)性、可擴(kuò)展性C++都遠(yuǎn)遠(yuǎn)好于C#版本。更不用說(shuō)可用性了(那個(gè)assign函數(shù)用起來(lái)有多難看)。
我知道,有些人還會(huì)說(shuō):貨幣太特殊了,在實(shí)踐中這種情況畢竟少見(jiàn)。沒(méi)錯(cuò),貨幣是比較特殊,但是并沒(méi)有特殊到獨(dú)此一家的程度。我曾經(jīng)做了一個(gè)讀取腳本中的圖形信息,并繪圖輸出的簡(jiǎn)單案例,以展示OOP的一些基本概念,用于培訓(xùn)。但如果將其細(xì)化,可以開(kāi)發(fā)出一個(gè)很不錯(cuò)的腳本繪圖引擎。其中,我使用了組合遞歸、多態(tài)和動(dòng)態(tài)鏈接,以及類(lèi)工廠(chǎng)等技術(shù)。就是那個(gè)類(lèi)工廠(chǎng),由于我使用了模板,使得類(lèi)工廠(chǎng)部分的代碼減少了2/3,而且沒(méi)有重復(fù)代碼,更易維護(hù)。關(guān)于抽象類(lèi)工廠(chǎng)的GP優(yōu)化,Alexandrescu在其《Modren C++ design》中,有更多的案例。同樣的技術(shù),還可以推廣到業(yè)務(wù)模型的類(lèi)系統(tǒng)中,優(yōu)化類(lèi)工廠(chǎng)的代碼。
如果還不滿(mǎn)意,那么就去看看boost。boost的很多庫(kù)實(shí)現(xiàn)了幾乎不可想象的功能,比如lambda表達(dá)式、BGL的命名參數(shù)等等。它為我們很多優(yōu)化軟件代碼新思路,很多技術(shù)和方法可以促進(jìn)我們大幅優(yōu)化代碼,降低開(kāi)發(fā)成本。
最后,如果你認(rèn)為C#的最大的優(yōu)勢(shì)在于.net平臺(tái),那我可以告訴你,這個(gè)世界上還有一種東西叫C++/CLI,完全可以滿(mǎn)足.net的開(kāi)發(fā),而且更好,足以擦干凈.net那骯臟的屁股。不過(guò),這將會(huì)是另外一個(gè)故事了…