C++使用經驗總結

寫在前面

借著公司內和其他小組的一個分享,把自己幾年來C++開發的一點思考總結一下。全篇沒有高屋建瓴的觀點,基本都是些細節方面的注意事項。希望能給大家提供一點幫助。

構建工具

C/C++世界里有不少的構建工具:make、autotools、scons、CMake、Bazel。但近幾年比較流行的,也就是CMake和Bazel。所以這一部分,也就大概對比下這兩個工具吧。

究竟該選擇哪個工具,我覺得可以從如下幾個方面來對比一下:

1. 上手難度

因為Bazel采用了類似Python的語法,所以其學習曲線相比CMake要平緩一些。但當我們考慮上手難度時,除了學習曲線之外,還要考慮文檔的完備性、該工具的通用性等各個角度。當綜合考慮時,我覺得CMake是一個盡管保守但仍舊不錯的選擇。主要原因就在于,CMake幾乎已經成為現在C++的事實標準。使用CMake,就意味著:

  • 你可以把你熟悉CMake的技能用在折騰別的C++項目上。而這點之所以重要,是因為你在利用某個第三方庫的時候,往往需要大概研究下它的編譯過程。
  • CMake的官方文檔和stackoverflow上的問答也比較完善。一旦遇到一個問題,往往通過搜索引擎能快速的得到答案。

另外,從設計理念上來看,CMake提供的解決方案是改革式的:它并沒有提供一個全新的解決方案,而是和Make、Visual Studio或者其他現有的構建工具來結合使用的。而這就使得你無需丟棄在其他工具上所積累起來的開發經驗——例如你熟悉make工具,哪怕是一個CMake維護的項目,你也可以毫不費力就知道如何來查看編譯參數,以及控制編譯并發度等等。

而對于Bazel則不是如此。Bazel完全以革命者的姿態完整提供了一整套解決方案,所有的使用細節你都要從頭開始。加上文檔的匱乏,這就使得你也得花上一段時間,才能熟悉Bazel。

2. thirdparty的管理

Bazel內置了對thirdparty源碼級別依賴的支持

  • thirdparty可以是用Bazel構建的,也可以不是。對于非Bazel項目,你需要額外為其添加一個Bazel的描述文件。
  • thirdparty可以是一個本地項目,也可以是一個git倉庫或者http鏈接

所以總的來看,Bazel對thirdparty支持還是非常友好的。

就這點對比來看,CMake其實做的是不太好的。CMake盡管也有ExternalProject的feature,但根據實際經驗來看,使用和維護都比較的復雜。所以我還是更傾向于寫幾個腳本來下載和編譯這些thirdparty依賴。

這里可以拿我參與維護的Pegasus項目為例。在該項目中,我們依賴了幾個不同類型的項目:

  • 從構建工具上來看,這些依賴有使用CMake的,有使用make的,有使用autotools的
  • 從來源上來看,有的依賴來自git倉庫,有的來自http鏈接,有的則是從一個大的項目里面挑選了一個更小的模塊使用
  • 從代碼的使用方式上來看,有的是直接拿來用,有的還需要稍微修改下源代碼。

而通過shell腳本,這些各種各樣的場景我們都能非常方便、直接、易維護的得以支持。

3. 其它

Bazel和CMake當然還有些其它方面值得對比,但并非一些通用的點,這里就簡單列舉下,不再詳細展開了:

  • IDE集成
  • 緩存編譯結果,從而加速編譯過程
  • 多語言混合變成的支持
  • 分布式編譯
  • 跨平臺的支持

再補充一個別人的討論鏈接, 有需求的可以參考一下。

編程規范

強烈推薦Google C++ Style。盡管它禁止了很多C++ feature而被很多人黑的很慘,但從工程的角度而言,它的確提供了非常多極其中肯的建議。說到底,編程規范的存在,主要就是可以讓水平參差不齊的工程師們,可以在一起協作出風格較為一致的項目來。

也存在一些工具可以對google規范進行檢查:

因為google的規范文檔對C++ feature的取舍原因講的非常好,這里就不再贅述了。唯一想補充的是異常

  • C++在語法層面對異常支持不太友好:你無法通過函數簽名來得知一個函數到底會拋出哪些異常。例如:
    void GetSomeResource(const char* resource_name);
    
    如果這個接口沒有良好文檔或注釋,并且也沒有代碼可翻時,你在調用這個接口時很有可能會漏掉一些錯誤情況——因為它可能拋出異常。更要命的是,一個疏于捕獲的異常一旦觸發,線上的程序就會crash。
    其實解釋這么多,大家只要和Java中的異常機制對比一下,就高下立判了。對于這個話題,王垠的這篇博客值得一看的。
  • 在運維Pegasus項目時,遇到過一個老版本glibc的bug:如果多個線程同時拋出異常,程序會陷入死循環。這個bug的發現也是個有趣的過程,后面我專門寫篇文章展開吧。
  • 在禁用異常后,程序就只能用錯誤碼來進行錯誤處理。對于很多項目,大家都采用一套類似的范式,可以參考tensorflow的做法

C++的新特性

如果能使用C++的新特性,當然是盡量使用的好。我自己在開發中,覺得非常方便必須使用的新特性有:

  • 智能指針
  • 右值,以及C++14中右值得capture
  • lambda, bind
  • initialize list

想補充說一下的是auto,我自己不是特別喜歡這個feature,也非常贊同google規范中的對auto的限制:僅當可以提高代碼可讀性時,使用auto

這里不由得就想扯起java 10中的var。雖然能方便開發,但覺得更多的是會被濫用。而一個可能被濫用的feature,還不如沒有的好。

第三方utility

在做項目開發的時候,一般會有很多瑣碎的需求,從而也需要很多utility工具包。這里把我遇到的一些需求整理一下:

  • 算法和數據結構:stl, boost
  • 錯誤碼管理:參見tensorflow
  • C語言的字符串封裝:string_view
  • 字符串的各種操作、轉換、打印:可以多翻翻abseil, 以及folly,另外也推薦fmtlib
  • 線程安全的、無鎖的數據結構、線程池: folly
  • google全家桶:gtest,gflags, glog, protobuf, grpc

最后,也推薦下kudu這個項目,里面有自己實現的一些工具包,以及對google開源項目中utility的整理

單元測試

每個程序員都討厭寫測試。就我自己而言,我覺的單元測試的目的有以下幾個:

  • 確保功能的實現和預期一致
  • 防止程序在重構的時候出問題
  • 給模塊的使用者,提供使用示例

值得一提的是,對于C++項目,除了功能性測試之外,你最好還能讓你的單元測試通過一些自動化工具的檢測,如:

寫在最后

自己的整理這些內容時,腦子里反復縈繞的一個問題是:我們在開發一個項目時,所要遵守的各種流程和規范到底是不是真的有必要的?說的更直白一點就是,“代碼潔癖”這東西到底有沒有意義?

我的看法是:代碼潔癖不是一個原則,而是在投入和產出上的一種權衡。如果僅僅快速試錯,那么就不需要維持代碼潔癖,因為你完全不知道你今天寫的代碼究竟能存活多久。而如果是一個馬拉松式的項目,代碼潔癖就值得維持,因為它對于項目的維護的確很有意義。

最后,貼一個自己比較喜歡的C++博客

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,829評論 25 708
  • 用兩張圖告訴你,為什么你的 App 會卡頓? - Android - 掘金 Cover 有什么料? 從這篇文章中你...
    hw1212閱讀 12,825評論 2 59
  • 1、通過CocoaPods安裝項目名稱項目信息 AFNetworking網絡請求組件 FMDB本地數據庫組件 SD...
    陽明AGI閱讀 16,003評論 3 119
  • 我是展鵬教育的大邢老師,這是我加入日記星球的第21篇原創日記。 今天展鵬教育旗下的展鵬作文、佳一數學秋季班課程全部...
    大邢老師閱讀 342評論 0 2
  • 兒子最近太沉迷于手機,我有點不高興,問題:你是想媽媽還是想媽媽的手機啊,他說:我是想你這個人,于是摟著我的脖子,開...
    薇薇shaw閱讀 125評論 0 0