??
谷歌C++風格文檔真是一個好東西,為C++開發(fā)提供了一個便捷高效又有無數(shù)人在實踐和驗證的白皮書,雖然其中并不是所有說法都是客觀的,但是既然是經(jīng)過谷歌這樣的公司投入實際應用的,那總不會有很大的壞處,至少不會給你帶來麻煩,所以我個人一直都比較堅持使用這套風格。
但是注意對于Windows程序員,對于谷歌這套風格來說,確實是有點不適合,比如異常,比如多重繼承,比如縮進、換行符等,因為Windows自成一體系以及經(jīng)歷了歷史的沉淀,當你接手一個項目時發(fā)現(xiàn)縮進都是Tab換行符都是Win樣式,那你也不可能就改成2個空格,這樣會讓code base變得無比混亂,所以在實際項目中使用谷歌C++風格的時候,視目前情況來確定哪些風格可用,哪些應該不用就是了。
- 頭文件原則 -
1、如果要使用一個聲明在某個頭文件中的函數(shù),應該總是include那個頭文件,也就是如果你實現(xiàn)一個函數(shù),應該將其實現(xiàn)在一個.c文件中,然后把函數(shù)聲明暴露在.h文件,而外部使用者應該include這個.h文件。
2、使用類模板的時候,應該include這個類的頭文件。
3、如果使用的是一個普通類,那向前聲明而不include類頭文件是可以的,但是你要確定向前聲明來使用不會出現(xiàn)其他問題,如果你不確定,那遵循一律include頭文件原則。
頭文件的包含順序:
1、C語言庫
2、C++的庫
3、項目中的第三方庫的頭文件
4、當前項目的頭文件
而include組織原則應該按照當前項目中的目錄樹結(jié)構(gòu)來組織,比如項目代碼“src/core/log.h”,在include時則應該是“core/log.h”。
- 命名空間 -
可以在源碼文件(CPPCC)中使用【未命名空間】來避免運行時的名稱沖突,注意命名空間格式不縮進。
- 類的一些注意 -
原則:應避免在類的構(gòu)造函數(shù)中進行復雜的初始化,尤其是是調(diào)用可能初始化失敗的函數(shù)或者是調(diào)用虛函數(shù)。
構(gòu)造函數(shù)失敗的情況除了拋出異常沒有其他辦法知道是否初始化失敗,而谷歌推薦不使用C++異常處理;
而如果構(gòu)造函數(shù)初始化失敗,我們得到的這個新的類的內(nèi)部狀態(tài)是“不清不楚”的,我們不知道是否該繼續(xù)調(diào)用此類的其他方法,還是干脆銷毀類;
如果在構(gòu)造函數(shù)中調(diào)用虛函數(shù),那就會依賴于子類的虛函數(shù)實現(xiàn),出現(xiàn)不可控制的情況,但是如果有這種奇葩的需求的話可以例外。
原則:寫一個默認的構(gòu)造初始化函數(shù)。
如果類有成員變量,你應該寫一個默認的構(gòu)造函數(shù)用于將這些變量進行初始化,否則編譯器會生成類似malloc這樣的挫B構(gòu)造函數(shù)。
在創(chuàng)建新的對象的時候?qū)⑵淠J初始化為一個“無效”的狀態(tài),以方便錯誤判斷,但是對寫這個類的人來說,這個工作顯得有多余。
而如果你的類定義了成員變量但是沒有初始化構(gòu)造函數(shù),則編譯器提供的默認初始化函數(shù)可能把你這些變量初始化為一個未知的值狀態(tài)。
原則:使用顯示構(gòu)造函數(shù)。
為了避免隱式的類構(gòu)造,應使用explicit關鍵字,具體大家可以百度此關鍵字。
原則:盡量不要使用復制構(gòu)造和賦值運算符來復制對象。
賦值構(gòu)造函數(shù)在編譯器處理時可以帶來一點好處,現(xiàn)代編譯器會有一些特定的優(yōu)化,當然主要是很多時候?qū)懫饋砗芩?br />但是只有小部分類是可復制創(chuàng)建新的對象的,大部分類都不需要這樣的功能,很多情況下指針和引用可以部分的替代這樣的復制構(gòu)造而且可以帶來更高效率的性能。
如果你的類是可以復制創(chuàng)建的,則最后應該提供一個CopyFrom或者CopyTo方法,而不是使用復制構(gòu)造函數(shù),因為這些方法不能被隱式調(diào)用,在調(diào)試的時候也能更快的定位處理點,但是為了某些情況,比如STL兼容的問題,你可以提供復制構(gòu)造函數(shù)來包裝這些方法。
這里還有一個建議,如果你的類不需要復制構(gòu)造函數(shù),那你應該顯示禁止它,這樣就可以在某些不可預料的隱式使用的時候讓編譯時出現(xiàn)錯誤。
原則:盡量使用class,而不是struct。
在C++中,class和struct的區(qū)別僅僅是默認訪問權限上的區(qū)別,他們幾乎就是一樣的。
但是struct應該盡量使用在數(shù)據(jù)對象的存改上,如果struct的功能提示到了“方法”級別,則應該使用class來替換struct,struct應該保持它作為數(shù)據(jù)體存儲空間的純潔性。
比如當有構(gòu)造函數(shù)、析構(gòu)函數(shù)、初始化方法的時候,則不應該使用struct。
如果不知道該用class還是struct,那就用class吧。
(不過,為了和STL保持一致性,應該使用struct而不是class來實現(xiàn)仿函數(shù)特性)
- 繼承 -
原則:不要過渡使用繼承,用組合會更好。
原則:如果有必要的話,將你類的析構(gòu)函數(shù)寫成虛的,如果你的類中有虛方法的話,那這個類的析構(gòu)函數(shù)必需應該是虛的。
原則:對于可能被子類使用的函數(shù)聲明為protected方法,要限制對它們的使用,而要注意每個類的數(shù)據(jù)成員應該是private的。
原則:在子類重定義一個虛函數(shù)來實現(xiàn)繼承時,應該顯示寫virtual關鍵字標識這個是虛繼承來的函數(shù),原因是,如果virtual關鍵字沒了,則閱讀者就要去確定這個方法是不是虛繼承來的。
- 多重繼承 -
原則:應該盡量避免多重繼承,而換為使用組合接口實現(xiàn)。
原則:我們只允許有實現(xiàn)的基類不超過一個的多重繼承(2重繼承),所以其他的應該都使用接口來實現(xiàn)。
我們將基類分為有實現(xiàn)和無實現(xiàn)(純接口)二種。
如果一個類滿足下面的情況,它就是一個純接口類:
1、它只有公開的純虛方法(= 0),和靜態(tài)方法;
2、它沒有非靜態(tài)的數(shù)據(jù)成員;
3、它不需要定義任何構(gòu)造函數(shù);
一個接口類不能生成一個新的實例對象,為了保證這個接口所有的實現(xiàn)都可以被直接銷毀,這個接口必需有一個虛的析構(gòu)函數(shù)。
原則:用interface這樣的關鍵字來表明一個類是一個接口類,同時禁止向其中添加方法實現(xiàn)或者封靜態(tài)的數(shù)據(jù)成員。
- 重載操作符 -
原則:除非有特殊情況,不然不要重載操作符。
1、重載操作符會誤導閱讀者的感覺特別是新手,他會認為這個操作十分廉價;
2、重裝操作符在尋找位置的時候不好找,因為代碼都是==、++;
3、重載操作符在某些情況下可能沖突或者導致bug;
一般來說不要重載操作符,而應該使用像Equals()這樣的方法來進行類似操作符的比對,如果這個類有向前聲明,就不要重載危險的一元操作符&。
但是也有一些特殊情況,比如需要跟STL交互,則需要實現(xiàn)==這樣的操作符或者仿函數(shù)的時候,那可以允許。
- 類的訪問控制 -
原則:類的數(shù)據(jù)成員應該為私有的,要暴露應該提供SetGet方法,類似C#中的屬性系統(tǒng),對C++來說,SetGet方法應該盡量保證inline。
- 類的聲明順序 -
原則:在類中的聲明順序應該為,public高于private,methods高于members。
你定義的類應該從public成員開始,接著是protected,然后是private。
在每個部分中,應該按照下面的順序,比如:
private
?- 自定義的typedef或者enums。
?- 常量(static const)。
?- 構(gòu)造函數(shù)
?- 析構(gòu)函數(shù)
?- 方法,以及靜態(tài)方法
?- 數(shù)據(jù)成員(除了靜態(tài)常量成員)
友元聲明應該總是在private部分。
- 所有按引用傳遞的參數(shù)都應該是const的 -
原則:我們堅持引用傳遞使用const,而如果要修改,則應該使用指針。
例如:void foo(const String& in, String* out)
- 函數(shù)重載 -
原則:只有當閱讀者一看就知道這個函數(shù)執(zhí)行了什么,而不需要去看哪個版本的函數(shù)被調(diào)用了,不然不要使用函數(shù)重載。
重載能令代碼更直接美觀,對于模板化的代碼重裝也是必需的。但是如果一個函數(shù)只通過參數(shù)來區(qū)別各個重載的版本,則需要去理解C++復雜的匹配優(yōu)先原則才能正確的使用和閱讀。而如果一個派生類只重寫了一部分函數(shù),許多人也會被繼承的語義所混亂視覺。
如果你想重載一個函數(shù),考慮一下將函數(shù)的名字寫成帶有一定的參數(shù)信息,比如AppendString和AddInt,而不是用一個Append或者Add去處理各個類型。
- 函數(shù)的默認參數(shù) -
原則:盡量不使用函數(shù)的默認參數(shù),這可能會導致和重載函數(shù)混亂。
- 不允許使用變長數(shù)組和alloca函數(shù) -
原則:變長數(shù)組并不是C++標準的一部分,部分編譯比如MSVC并不對其進行支持。
- 友元 -
原則:如果可能,盡可能的使用public方法來進行訪問控制的溝通。
- 異常 -
(這里爭議比較大,C++異常我實在不好評價什么,看你自己了。)
原則:我們不使用異常。
- RTTI -
原則:盡量不使用RTTI,除非在某些特殊的情況比如單元測試的時候需要便捷也不考慮性能效率的測試父類型或者判斷類型。
因為RTTI會時代碼難以維護,它會使代碼遍地都是if和case,而且如果要頻繁的檢查對象類型就證明目前的架構(gòu)設計其實是有問題的。
- 類型轉(zhuǎn)換 -
原則:使用C++風格的類型轉(zhuǎn)換,而不是使用C語言風格的。
這樣可以使在批量搜索代碼的時候快速定位類型轉(zhuǎn)換關鍵字,也更加鮮明,主要是符合C++的特性。
1、使用static_cast進行數(shù)值的轉(zhuǎn)換,比如int64轉(zhuǎn)換int32,或是顯示將一個類的指針轉(zhuǎn)換為它的子類的指針;
2、使用const_cast去掉const特性;
3、使用reinterpret_cast做類似C語言的不安全強制轉(zhuǎn)換;
- IO和Stream -
原則:如果成員有IO和Stream操作,則它應該提供一套IO的ReadWrite接口。
- ++i和i++ -
原則:忽略返回值的時候,前綴形式(++i)至少不會比后綴形式效率低,甚至更高,因為后綴形式自加或者自減,需要保存i的原始值作為表達式的值,如果i是迭代器或者其他被標準類型,復制操作的代價可能更大。
所以我們應該盡量使用前綴++形式,特別是使用STL迭代器的情況。
- 數(shù)和類型 -
原則:0用于整數(shù),0.0(f)用于浮點數(shù),nullptr用于指針,'