庫是語言的重要組成部分。對於任何一門語言和開發平台的學習和掌握,都離不開對庫的熟練運用。我們說,C語言有CRT和Posix API,Java有J2SE/J2ME/J2EE,C#有.NET Framework,Python也有自己的Library。
對於大多數語言而言,對於語言附帶的標準庫的使用,簡直是天經地義的事情。但是就是這樣一個人所盡知的問題,在C++卻出現了嚴重的分化。不要說作為準標準的Boost,就連標準的STL,都會被無數人以“低效”為由拒絕使用。
之前在一個文章上,和別人爭論這個問題爭論了很長時間,當然,最終一定是不了了之的。C++標準庫的使用居然變成了一種信仰和哲學問題,這也算是電腦語言當中的一朵奇葩了。
這裡我將這個文章當中比較有價值和代表性的留言發出來,也是在這裡與大家交流一下。
——————————————————————————————————————————
w:
(做一個遊戲引擎)很猶豫,是自己寫還是用stl和boost?
g:
都是自己寫,不用其它的。
qj:
不需要通過DLL匯出的情況下,我用STL用的還是蠻多的。
但是一般不使用boost,boost雖然優秀,但是它的大部分內容都有些“高手的玩具”,或者“時尚人士炫耀的工具”,或者“為了什麼什麼而牽強附會” 這樣的感覺,剩下那些實用的,要麼進了標準庫,要麼有替代品,要麼不用也罷,所以這種情況下,我就不想給自己的項目添加那麼大的一個依賴項了……
gmm:
我的引擎用,而且是大量使用。大部分人都是因為不瞭解而產生恐懼,繼而不用的。
qj:
一般來說STL還是可以用用的。但是如果你是寫對效能很敏感的核心代碼的話,還是不要用STL,STL會做一些無謂的bounds check而影響效能,但最主要的是記憶體配置,很多時候需要使用特殊的記憶體 Clerk,但是STL的記憶體 Clerk設計是非常失敗的,容器的記憶體 Clerk居然是跟類 型綁定的,實在是太扯淡了。
至於Boost就沒必要去用了,有些庫屬於文法糖,例如lambda,functional,smart ptr之類,只是寫法上更簡潔一些,但代價是增加了編譯時間,而且受制於C++本身的限制,有些寫法其實還是比較彆扭的,還不如直接用其他更進階的語言。 Boost試圖把很多FPL特性引入到C++裡,根本就是畫虎不成反類犬。當然有些庫還是幹了些實事的,比如Asio之類,不過在我看來這些庫的整體設計 其實都是比較糟糕的,估計作者們都把心思花在如何堆積技巧上了,而把如何設計好的程式架構忘在腦後了。
3222:
說的好,頂了,我做自己的引擎深有體會!STL 因為是公用庫,是要考慮很多情況的,自己引擎的stl,只是針對自己的,速度和效能快很多,而且還有助於管理自己分配記憶體
sssa:
有的stl的實現確實會有越界的檢查例如 stl port。不過我記得是不是只有在debug下才會這樣?
記憶體適配器確實有必要自己實現一個。
我倒不認為stl本身有什麼錯,如果發現瓶頸在stl上 我覺得很可能是沒有使用正確或者從設計上有失誤,我不認為能通過自己寫一個容器就能大幅度提高效能。
可能出現的情況是 我只需要一個容器的某個很簡單的功能但是stl提供了比所需要多的功能,而我不得不為這多出來的功能買單,但是即使著這種情況 我認為也能夠從stl其他的用法中得到改善。
一笑:
我們全是自己寫的,當然加了宏,可以方便的轉成STL.實驗發現,用自己的編譯出來的EXE比用STL的小了20多KB.我們在自己的模板庫裡加入了大量 assert,比如數組[]操作符.還有string的c_str().......map則改用雜湊實現.同時也自己做了記憶體管理,我們的vector 最大的不同是,它不是以倍數增大的,而是以實際分配記憶體的大小.比如我需要4位元組,記憶體管理器實際分配了16位元組,vector的容量也增大到16 了.......目前來看效果很不錯.
mxh:
stl/boost是多少人嚴格測試發布的庫,怎麼會比自己拍腦袋寫出來的robust差?
我一般看到用stl/boost導致的效能問題,都是設計不好或者不會用。
ldd:
有點。vector倍數增長是實際證明的最高效增長方式,如果你需要節省記憶體,它可以compact到實際記憶體使用量量。另外hash_map和 unordered_map現在的STL基本都有。
gmm:
總結一下:
"我就不想給自己的項目添加那麼大的一個依賴項了"
boost是由很多基本獨立的小庫組成的,又不是用一個全都得放進去,我不知道大在哪裡了。
"STL會做一些無謂的bounds check而影響效能"
STL不會,VC的STL實現會,而且可以關掉,尤其是VC10的,在release下不會檢查。
"但最主要的是記憶體配置,很多時候需要使用特殊的記憶體 Clerk,但是STL的記憶體 Clerk設計是非常失敗的,容器的記憶體 Clerk居然是跟類型綁定的,實在是太扯 淡了。"
stlport的方法就是2層的allocator,一層是類型無關的,上面一層是類型相關的。標準規定了類型相關,不等於就不能有個類型無關的阿。只要 介面和allocator一樣,自己寫個類型無關的完全可以。
"有些庫屬於文法糖,例如lambda,functional,smart ptr之類"
functional,smart ptr是文法糖?
"我們在自己的模板庫裡加入了大量assert,比如數組[]操作符"
VC8+的STL也有,而且有宏可以控制是否執行檢查
"map則改用雜湊實現"
map有map的用處,hash有hash的用處,不是替換的關係
"同時也自己做了記憶體管理,我們的vector最大的不同是,它不是以倍數增大的"
首先vector不一定是倍數增長的,也有別的增長方式。第二預留一定的空間可以讓new/delete次數降低,否則人家沒必要這麼做。
qj:
"STL會做一些無謂的bounds check而影響效能"
STL不會,VC的STL實現會,而且可以關掉,尤其是VC10的,在release下不會檢查。
bounds check是跟STL實現相關的,在VC裡當然也是可以關掉的。我以前一直以為STL在Release下不會做bounds check,直到有一次反組譯碼了一下代碼,再查看了一下原始碼才發現STL裡有bounds check。當然這個不是什麼大問題。
"但最主要的是記憶體配置,很多時候需要使用特殊的記憶體 Clerk,但是STL的記憶體 Clerk設計是非常失敗的,容器的記憶體 Clerk居然是跟類型綁定的,實在是太扯 淡了。"
stlport的方法就是2層的allocator,一層是類型無關的,上面一層是類型相關的。標準規定了類型相關,不等於就不能有個類型無關的阿。只要 介面和allocator一樣,自己寫個類型無關的完全可以。
2層allocator也幫不上忙,其實我需要的是給某個容器執行個體指定一個allocator。比如我用某個list<int>容器,我希望 每個元素用我指定的allocator的來分配,這個allocator可以直接在棧上開闢一個臨時空間,等函數返回的時候一次性釋放掉,而不需要再一個 個的釋放。像這樣:
allocator alloc = allocator(_alloca(10000), 10000);
list<int> l = list<int>(alloc);
"有些庫屬於文法糖,例如lambda,functional,smart ptr之類"
functional,smart ptr是文法糖?
沒有smart ptr而用原生指標不會影響程式功能,只不過多寫幾行釋放代碼而已。functional也是一樣道理。這就是文法糖。
我:
去看看container的constructor吧。。。
還有,smart_ptr是文法糖。。。呃,我徹底無語了。
gmm:
"bounds check是跟STL實現相關的,在VC裡當然也是可以關掉的。我以前一直以為STL在Release下不會做bounds check,直到有一次反組譯碼了一下代碼,再查看了一下原始碼才發現STL裡有bounds check。當然這個不是什麼大問題。"
你是特指VC9吧。VC8和9我都會在release裡面定義_SCL_SECURE=0把check關掉。VC10預設就是release沒check 的。
"我希望每個元素用我指定的allocator的來分配"
這當然可以了,從來都可以,本來就可以。
"沒有smart ptr而用原生指標不會影響程式功能,只不過多寫幾行釋放代碼而已"
關於文法糖是什麼意思,相信你還得看看。你對這三個字的理解有誤。引用計數都稱文法糖的話,還有什麼不算?
我:
不是給語言增加新功能。而是庫層級沒有辦法實現,又不改變語言基本假設、主要方法論和開發範式的文法形式和文法元素,一般都稱之為文法糖。 smart_ptr並沒有對文法做出任何改善,而是側重在引用計數的功能關注點上,因此屬於庫的層級,不能稱作是文法糖。
qj:
補充幾點:
1、list是可以在建構函式裡指定分配器,但是類型定義裡必須帶上分配器的類型,這就是我之前說STL的容器類型是和分配器綁定的。
2、smart_ptr跟引用計數是兩碼事。COM也是引用計數的,但沒有規定COM就要用smart_ptr訪問。反之也沒規定smart_ptr必須 是引用計數的。
3、說這些東西是文法糖,因為這些東西在其他語言裡就是文法糖,他們不提供功能而只是使寫法更簡潔一些,Boost用庫的形式提供了這些文法糖。沒有 Boost實現的這些糖,單純使用C也可以做到這些功能,只是寫法上麻煩一些而已。如果你認為用庫實現的東西不叫文法糖,那我也沒意見,但是本質上他們是 一類東西。
xj:
要是stl/boost能像.net framework那樣統一就好了... boost用多了感覺代碼風格都變了, 完全像在使用另一門語言的樣子
我:
stl和boost大體上是統一的。
實際上STL和Boost針對Developer和User哲學是不一樣的。
對於Boost開發人員而言,強調的是代碼可讀,高效,強調元編程和編程技巧。
對於User而言,boost和STL分為四種風格。
第一種風格為Lib風格,以提供功能為主。例如Pool,Graph等,也包括樓上一直在爭論的Smart_Pointer和Asio。這一類風格的用 法,是典型的雙階段的,第一階段是型別特化,第二階段是基於編譯器/運行時介面的引用。STL和BOOST裡,大部分庫都是這樣的風格。這也是最容易使用 和使用頻率最高的風格。
第二種風格是文法糖類。for each等都屬於這一類。這一類庫,通常是按照As-is的方式使用的。
第三種風格是範式和方法論的拓展,即在C++中類比其他編程範式和方法論。例如spirit,lambda,proto。嚴格的說,boost.mpl也 可以歸屬此類。這一類庫的使用方式分為兩步,第一步是定製方言,第二步是使用方言。
第四類風格,是元編程。利用模板和宏進行編譯器推導,以實現代碼展開、選擇編譯等工作。典型的例子有 boost.pp,stl/boost.type_traits,enable_if等,這一部分對於一般使用者是可以不用的。
所謂的boost和stl風格不統一,大致上是因為stl僅包含第一種,而boost包含了全部四類的庫的風格。
實際上單就lib形式使用的庫而言,boost和stl風格幾乎是完全一致的。而且,所謂的編譯時間過長的問題,在lib一系的庫中,也基本上不存在。所 以所謂boost難用,只是眉毛鬍子一把抓,不會對庫進行分類的問題。並且,不管哪一類,boost都是強調介面對方便使用。只是不同層次上的庫,友好的 方面和友好程度是不同的而已。
至於說.net的形式統一,你覺得.net的reflection能夠和xml一類的在使用感覺上是一致的嗎?很顯然不可能。只不過C++的多範式設計, 加劇了這個問題而已。
qj:
看樓上說的頭頭是道,我也懶得再說什麼了。
我只想說的是,Boost基本的設計思路就是不正確的,即想追求簡潔性又要求通用性還要保持效能沒有損失,這麼多目標合在一起造就了boost這樣一頭四 不像的怪獸。正如linus對C++的批判所言,真正重要的是設計。一般來說,大部分的設計目標都不是正交的,比如為了效能就需要犧牲一些通用性,為了可 靠性就要降低一些系統的複雜性,所以設計的重點在於取捨,根據設計目標在通用性,效能,系統複雜度等多個因素上做出正確的取捨。boost的很多庫在設計 時為了同時滿足多種設計目標,所付出的代價是大大提升了系統的複雜性,系統因此變得臃腫不堪,並且又企圖用編程技巧來掩蓋設計上的缺陷,產生出來的只是一 堆糟糕的設計。而且Boost的這種技巧淩駕於設計的風格,毒害了很多經驗不足的C++程式員,讓他們沉醉於構造那些花哨的模板技巧,而忘記了編程的本來 目的。
我:
我也確實不知道樓上還有什麼要說的。
很顯然,技巧是高手們使用的。所謂“被毒害”,只不過是因為模仿的火候不到,不能怪語言設計者和庫作者。我前面也說過了,你用Lambda和Spirit 的複雜度去討論Smart Ptr很顯然是一種愚蠢的不合時宜的行為。難道你覺得Smart Pointer,Pool的設計是臃腫的嗎?難道你覺得any的設計是多餘的嗎?難道boost提供的,TR1的Unordered Map的實現,是“拙劣的”嗎?那麼,sorry,即便是C函數庫,也不可能盡滿足你的要求。
庫在設計時的考量本身就是一種權衡,不管什麼庫都是。即便是CRT這樣的庫。Malloc一樣有諸多問題,因而才會有TCMalloc這樣的第三方庫。如 果你喜歡,你大可以用彙編去整一套你想要的東西出來。
可是你覺得這樣現實嗎?
Poc:
娃哈哈,天朝技術人員的老毛病了——靠企圖打到別人來證明自己的高明。
沒錯,任何東西都要辯證的看,但光辯證不唯物那就是詭辯了——說白了和潑婦吵架沒啥區別。
其實我是C控,希望的設計是用C作為介面的若干松耦合模組作為底層,然後使用指令碼語言或者帶gc的進階語言做上層邏輯。我是提倡用C的方式使用C++,但 並不是排斥C++的特性,當然某個東西用還是不用是得在充分瞭解之後了,在這之前更不敢妄加評論了。
qj:
Рос同學不必妄自菲薄,技術之爭在哪裡都有,國外的論壇上關於某項技術或者語言的爭論也是喋喋不休的,爭論只是形式不是目的,通過爭論加深對某些 東西的理解,同時可以聽到一些不同的聲音,這才是我們應該從爭論中得到的收穫。
關於boost的smart ptr我不認為他的設計是好的設計,boost的smart ptr的設計是個野心勃勃的設計,試圖提供一攬子的解決方案把所有可能的smart ptr特性都提供給使用者,比早年Loki中的smart ptr做的還要多。所帶來的問題是嚴重增加了使用者的學習成本,使用者需要考慮的因素太多,很容易用錯。比方說把單線程的share_ptr用在多線程環 境裡。smart ptr在我的工程裡也經常會用到,我通常會自己實現一個簡單的smart ptr,只為我需要的特性來設計,不到100行代碼就可以搞定。
gmm:
"比方說把單線程的share_ptr用在多線程環境裡"
shared_ptr是atomic的阿,不分單和多。
——————————————————————————————————————————————————
把這段話放在這裡的目的,不是說誰對或者誰錯,而是說闡明一下不同的人對STL和Boost的不同理解。當然,這裡面,有些是值得商榷的,而另外有一些,特別是qj的話中,是有不少錯誤存在的。一些錯誤我和gmm在回複中給予了糾正,另外一部分錯誤,可能就需要大家對模板和STL的代碼都有比較深入的瞭解才能發現了。
我的觀點很明確,對於BOOST和STL,國內的C++社區存在著太深的誤解和偏見。特別是Boost,由於這個庫設計技巧極為複雜,然而使用者介面卻又非常幹練易用。很多人便將Boost庫的使用難度和設計難度等同到一起,進而產生畏難情緒。
我始終堅持對於大部分人而言,學會使用STL和Boost的意義是不言而喻的,而且,它比學會STL和Boost本身的設計構建技巧重要的多。