這是作者為電子工業出版社出版的《Boost程序庫完全開發指南》寫的推薦序,此處節選了作者對在C++工程項目中使用Boost的看法。
最近一年(這篇文章寫于2010年8月)作者電話面試了數十位C++應聘者。慣用的暖場問題是“工作中使用過STL的哪些組件?使用過Boost的哪些組件?”。得到的答案大多集中在vector、map、shared_ptr。如果對方是在校學生,作者一般會問問vector或map的內部實現、各種操作的復雜度以及迭代器失效的可能場景。如果是有經驗的程序員,作者還會追問shared_ptr的線程安全性、循環引用的后果及如果避免、weak_ptr的作用等。如果這些都回答得不錯,進一步還可以問問如何實現線程安全的引用計數,如果定制刪除動作等等。這些問題讓作者能迅速辨別對方的C++水平。
作者之所以在面試時問到Boost,是因為其中的某些組件確實可以用于編寫可維護的產品代碼。Boost包含近百個程序庫,其中不乏具有工程實用價值的佳品。每個人的口味與技術背景不一樣,對Boost的取舍也不一樣。就作者的個人經驗而言,首先可以使用絕對無害的庫,例如noncopyable、scoped_ptr、static_assert等,這些庫的學習和使用都比較簡單,容易入手。其次,有些功能自己實現起來并不困難,正好Boost里提供了現成的代碼,那就不妨一用,比如date_time(注意boost::date_time處理時區和夏令時采用的方法不夠靈活,可以考慮使用muduo::TimeZone)和circular_buffer等等。然后,在新項目中,對于消息傳遞和資源管理可以考慮采用更加現代的方式,例如用function/bind在某些情況下代替虛函數作為庫的回調接口、借助shared_ptr實現線程安全的對象回調等等。這二者會影響整個程序的設計思路與風格,需要通盤考慮,如果正確使用智能指針,在現代C++里一般不需要出現delete語句。最后,對某些性能不佳的庫保持警惕,比如lexical_cast(一個用于在不同數據類型之間進行轉換的通用工具,如將字符串轉換為數字,將數字轉換為字符串,以及其他基本數據類型之間的轉換)。總之,在項目組成員人人都能理解并運用的基礎上,適當引入現成的Boost組件,以減少重復勞動,提高生產力。
試舉一例:正則表達式庫regex對線程安全的處理。早期的RegEx class不是線程安全的,它把“正則表達式”和“匹配動作”放到了一個class里邊。由于有可變數據,RegEx的對象不能跨線程使用。如今的regex明確地區分了不可變(immutable)與可變(mutable)的數據,前者可以安全地跨線程共享,后者則不行。比如正則表達式本身(basic_regex)與一次匹配的結果(match_results)是不可變的;而匹配動作本身(match_regex)設計狀態更新,是可變的,于是用可重入的函數將其封裝起來,不讓這些數據泄露給別的線程。正是由于做了這樣合理的區分,regex在正常使用時就不必加鎖。
Donald Knuth在《Coders at Work》一書里表達了這樣一個觀點:如果程序員的工作就是擺弄參數去調用現成的庫,而不知道這些庫是如何實現的,那么這份職業就沒啥樂趣可言。換句話說,固然強調工作中不要重新發明輪子,但是作為一個合格的程序員,應該具備自制輪子的能力。非不能也,是不為也。
C/C++語言的一大特點是其標準庫可以用語言自身實現。C標準庫的strlen、strcpy、strcmp系列函數是教學與練習的好題材,C++標準庫的complex、string、vector則是class、資源管理、模板編程的絕佳示范。在深入了解STL的實現之后,運用STL自然手到擒來,并能自動避免一些錯誤和低效的用法。
對于Boost也是如此,為了消除使用時的異或,為了用得更順手,有時我們需要適當了解其內部實現,甚至編寫簡化版用作對比驗證。但是由于Boost代碼用到了日常應用程序開發中不常見的高級語法和技巧,并且為了跨多個平臺和編譯器而大量使用了預處理宏,閱讀Boost源碼并不輕松愜意,需要下一番工夫。另一方面,如果沉迷于這些有趣的底層細節而忘了原本要解決什么問題,恐怕就舍本逐末了。
Boost中的很多庫是按泛型編程(generic programming)的范式來設計的,對于熟悉面向對象編程的人而言,或許面臨一個思路的轉變。比如,你得熟悉泛型編程的那套術語,如concept、model、refinement,才容易讀懂Boost.Threads的文檔中關于各種鎖的描述。對于熟悉STL設計理念的人而言,這不是什么大問題。
在某些領域,Boost不是唯一的選擇,也不一定是最好的選擇。比如,要生成公式化的源代碼,坐著寧愿用腳本語言寫一小段代碼生成程序,而不用Boost.Preprocessor(Boost C++庫中的一個組件,旨在提供一組預處理器宏,用于在編譯時生成重復代碼、元編程和其他需要在預處理階段處理的任務);要在C++程序中嵌入領域特定語言,作者寧愿用Lua或其他語言解釋器,而不用Boost.Proto(允許用戶定義自己的表達式語法,以及對應的語法規則和操作。這使得用戶可以根據自己的需求定義符合領域特定需求的表達式語法,從而編寫更加簡潔和易讀的代碼);要用C++程序解析上下文無關文法,作者寧愿用ANTLR(ANother Tool for Language Recognition,一個用于構建語言識別器、解析器和翻譯器的工具)來定義詞法與語法規則并生成解析器(parser),而不用Boost.Spirit(一個用于構建解析器和生成器的工具庫,能夠讓開發者使用C++中的語法進行遞歸下降解析和生成操作)。總之,使用Boost時心態要平和,別較勁去改造C++語言。把它有助于提高生產力的那部分功能充分發揮出來,讓項目從中受益才是關鍵。