說幾個STL的缺點吧,雖然都是在比較極端的情況下出現,但是對於一些大項目還是會遇到的
1. 代碼膨脹問題
每一個實例化過的模板類,都會膨脹出一份獨立的代碼,比如
std::vector<std::string>, std::vector<int>,編譯后會產生兩份代碼,在VC2008下,每份代碼大約是3-4kb,這是因為vector比較簡單代碼少,如果是map則會產生30-50kb的代碼,因為map里有個復雜的紅黑樹。對於數據處理類的代碼里一般會定義很多種不同的結構體,不同的結構體放到不同的容器里,就會實例化出很多個類的代碼,我見過一個項目里,這樣的vector就有數百個。
2. 內存使用效率問題 (以vc++2008為例)
stl在內存使用效率上是比較低效的,比如std::string,它的sizeof大概是28,因為它有一個內置的16字節數組,用來做小字符串優化的,就是說低於16字節的字符串都會至少占用28字節內存,如果剛好17字節字符串,則會占用28字節+額外分配的字符串內存,額外分配的內存是一個堆塊,又有很多浪費,相比用一個char *存儲字符串大約多占用了一倍內存。
還有map<>,每一個map的node都是一塊獨立分配的內存,如果是 map<int, int>呢,那就很悲劇了,為了存一個int要消耗幾十個字節,很浪費的。
如果元素數量有百萬級,那么內存占用就很可觀了,這種情況下建議自己實現allocator,做內存池。
3. deep copy問題
讓兩個容器的實例做賦值操作,看起來就一條語句,實際上容器里的每個元素都執行了一次賦值操作。如果容器里有百萬級的數據,那么一個等號就產生了幾百萬次的構造和析構。
傳遞參數的時候一定要用 const 引用,賦值可以用 swap代替。
4. 隱式類型轉換
比如 有個函數
void doSomething(const std::string &str);
調用的時候
doSomething("hello");
能編譯執行,但是會產生一個臨時的匿名的std::string實例,把"hello"復制一遍,然后在調用完成后析構掉。如果這個發生在循環體內部有可能影響性能。
以上這些問題,在小程序里或者數據規模不大的時候,比如容器內元素只有幾千這個規模,都不是什么大問題,那時開發效率才是重點,但是一旦有大數據stl容器會成為性能瓶頸的。
我並不是主張不用STL,而是要充分了解STL的優缺點,根據應用場景做選擇。
***********************************************************************************************************************************************************
最初開始禁用 C++ STL,更多地是早期項目編碼實踐中留下的慣例,被后來的程序員繼承下來。老項目中這種選擇尤其地多。不過如果有人將其上升到公司行為在不同項目中全面禁用 STL,則沒有必要,而且我傾向於做這種決定的人並不理解 C++ 編譯系統。
一般來說,項目中禁用 C++ 多見於兩種具體場景:或者項目的產出產品為函數庫,或者需要引用第三方函數庫。具體地來說,有三個主要原因:
第一個原因是二進制邊界混亂。對需要在項目中使用第三方函數庫的程序員來說,二進制邊界是個頭痛的問題。C++ 在這一方面本身就處理得不算好,加上模板后起到的是雪上加霜的后果。沒有經驗的程序員會貪圖方便而在公開頭文件中使用 C++ 模板,如果這時調用方的編譯器選項設置或 STL 版本和編譯方不同,那么就可能出現同樣的頭文件在不同的環境下二進制布局不符的情況。——順便說一句,在過去十年里,各個主流編譯器附帶的 STL 版本變化節奏不慢,所以這種由於編譯環境不同而導致的 bug 並不算罕見,但缺乏匯編知識的用戶難以排查。
第二個原因是不願使用異常。如今除了 Android 上的 STLPort 關閉異常,大部分主流 C++ STL 實現里都無法脫離異常使用 STL。異常帶來的問題主要是兩個:性能下降,代碼膨脹。這幾年 C++ 編譯器在性能方面的改進很多,good path 的性能問題已經基本沒有,但代碼膨脹問題卻沒有太多改善,甚至這個性能問題的一部分解決方案就是以代碼膨脹為代價。我寫過一篇短文比對過 Android 上 gcc 4.6 在有無異常的情況下的匯編代碼邏輯,可以看到,啟動異常時生成的匯編代碼量多出了相當一部分(我的例子中是 50%),用於處理各種隱含代碼中的異常問題。這一條在手機系統中有時候會引起意想不到的麻煩,比如軟件升級后導致 app 在低存儲容量的手機中安裝失敗。順便說一句,這個問題並不是 gcc 獨有,clang 上生成的代碼是一樣的。參考:http://dummydigit.net/posts/2014-01-01-23-30-1.html。
最后一個原因是 C 兼容。嚴格地說,STL 在這個問題上算是躺槍,這個坑在很多具體的場景中也是因為異常而引入,但這個問題的麻煩程度比前兩個問題更高。比如 gcc 在編譯純 C 代碼時默認關閉 -fexceptions 選項,因此這樣編譯出來的代碼中沒有異常處理相關的棧展開。如果某個 C++ 項目引用了一個第三方 C 項目,它很難確保那個 C 項目給出的二進制代碼中正確進行了異常處理並保證代碼服從異常安全操作。這種場景下混用 C/C++ ,就可能在拋出異常時莫名其妙地崩潰或者出現 C 代碼區段中的資源泄漏,特別是 expat 那種大量利用回調的代碼結構。要規避這種風險並非不可能,但需要 C 的架構部分做修改,比如使用 DOM 那種樹形結構,這種做法對歷史項目而言又很難辦到。換言之,如果一個項目出於種種原因需要保持 C 兼容,而 STL 就屬於其中一個不可控的變數,與其相信程序員不犯錯,不如直接禁用更可控一些。參考:Code Gen Options
要解決二進制相關的問題很簡單:整個項目的所有相關代碼在同一個代碼基上編譯,強制打開編譯選項添加異常代碼,並去除一切二進制依賴。但對很多小公司來說,引入這樣的系統對配置管理的要求較高。如果一部分依賴關系來自自己並不了解的第三方代碼,輕易修改編譯選項可能帶來的風險與第三方代碼庫的規模成正比。退一步說,即便團隊里真的有強大的配置管理工程師能夠搞定一切,他們也不會有能力解決代碼膨脹問題,除非他們有權決定換一個編譯器。相比之下,前面朋友所說的所謂性能或者編譯出錯時糟糕的可讀性,在我看來反倒是次要因素,而且這些缺陷都正在新的編譯器中逐步得到解決或改善,比如 clang。
所以那些選擇禁用 STL 的早期項目負責人,總有一些確實的理由,沒有人那么別扭地想跟開發效率過不去。至於后來的人是真的仔細想過這些細節還是人雲亦雲,那是另一個問題。
****************************************************************************************************************************************************************
一些普遍解決方案和回答:
最安全的方式是不要跨 DLL boundary 使用 C++ 對象,尤其是 STL。
DLL 和調用 DLL 程序使用的是不是一個堆,取決於整個工程的 CRT 使用方式。如果 CRT 是動態鏈接的,那么程序本身和所有 DLL 都使用一個堆。如果 CRT 是靜態鏈接,那么必然是逐一靜態鏈接到程序本身和每個 DLL 上,那么每個模塊都是一個單獨的堆。
c++ - How can I use Standard Library (STL) classes in my dll interface or ABI?
一個原則是,你的DLL公共類的ABI必須是確定的,除非你自己改代碼,否則這個ABI不應該隨着DLL的編譯器版本而改變,但是STL做不到這一點,因為他的實現不受DLL作者控制。但是STL可以在DLL私有部分使用,只要其細節不被暴露出來即可。
推薦文章:"How to export an instantiation of a Standard Template Library (STL) class and a class that contains a data member that is an STL object“ - 微軟
可以,要注意一些細節,先占坑
已填坑
爪機碼字一大段直接沒了,我的心好痛
1.exe和dll全部使用動態鏈接
因為dll和exe是鏈接到同一個運行庫dll上的,因此使用的是同一份運行庫的代碼,內存分配是在一個堆上的。對於所有類所有特性都能跨二進制文件使用
2.只要任意一個使用了靜態鏈接,你就不能向上面一樣使用
原因:靜態鏈接會在生成的二進制文件里包含庫的代碼,那么你的dll和exe使用的其實是兩個運行庫的代碼,自然會有兩個不同堆的問題
解決方案:
1.對於dll中導出的對象避開所
有malloc(new)和free(delete)操作,dll中使用exe里的對象也一樣
2.將exe里和dll里的malloc(new)和free(delete)地址弄出來,人工避免混用
3.利用c++虛函數。由於虛函數的地址是在運行期綁定的,因此你可以利用這個特性來避免混用exe和dll中的malloc(new)和free(delete)。比如你在dll里new了一個class a並在exe里使用它,那你就不能在exe里直接delete之。你應該為a添加一個virtual的release
virtual void release()
{
delete this;
}
需要釋放資源的時候調用release。對於所有需要申請內存的操作同理。
然而對於string在靜態鏈接的情況下除了解決方法1都沒有什么卵用,除非你自己實現一個。
references:
http://www.zhihu.com/question/20201972
http://www.zhihu.com/question/32127579