為什麼很多公司不允許使用C++ 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 上生成的程式碼是一樣的。參考:
最後一個原因是 C 相容。嚴格地說,STL 在這個問題上算是躺槍,這個坑在很多具體的場景中也是因為異常而引入,但這個問題的麻煩程度比前兩個問題更高。比如 gcc 在編譯純 C 程式碼時預設關閉 -fexceptions 選項,因此這樣編譯出來的程式碼中沒有異常處理相關的棧展開。如果某個 C++ 專案引用了一個第三方 C 專案,它很難確保那個 C 專案給出的二進位制程式碼中正確進行了異常處理並保證程式碼服從異常安全操作。這種場景下混用 C/C++ ,就可能在丟擲異常時莫名其妙地崩潰或者出現 C 程式碼區段中的資源洩漏,特別是 expat 那種大量利用回撥的程式碼結構。要規避這種風險並非不可能,但需要 C 的架構部分做修改,比如使用 DOM 那種樹形結構,這種做法對歷史專案而言又很難辦到。換言之,如果一個專案出於種種原因需要保持 C 相容,而 STL 就屬於其中一個不可控的變數,與其相信程式設計師不犯錯,不如直接禁用更可控一些。參考:
要解決二進位制相關的問題很簡單:整個專案的所有相關程式碼在同一個程式碼基上編譯,強制開啟編譯選項新增異常程式碼,並去除一切二進位制依賴。但對很多小公司來說,引入這樣的系統對配置管理的要求較高。如果一部分依賴關係來自自己並不瞭解的第三方程式碼,輕易修改編譯選項可能帶來的風險與第三方程式碼庫的規模成正比。退一步說,即便團隊裡真的有強大的配置管理工程師能夠搞定一切,他們也不會有能力解決程式碼膨脹問題,除非他們有權決定換一個編譯器。相比之下,前面朋友所說的所謂效能或者編譯出錯時糟糕的可讀性,在我看來反倒是次要因素,而且這些缺陷都正在新的編譯器中逐步得到解決或改善,比如 clang。
所以那些選擇禁用 STL 的早期專案負責人,總有一些確實的理由,沒有人那麼彆扭地想跟開發效率過不去。至於後來的人是真的仔細想過這些細節還是人云亦云,那是另一個問題。