0. 扉頁
0.1 譯者前言
Google 經(jīng)常會發(fā)布一些開源項目, 意味著會接受來自其他代碼貢獻(xiàn)者的代碼. 但是如果代碼貢獻(xiàn)者的編程風(fēng)格與 Google 的不一致, 會給代碼閱讀者和其他代碼提交者造成不小的困擾. Google 因此發(fā)布了這份自己的編程風(fēng)格指南, 使所有提交代碼的人都能獲知 Google 的編程風(fēng)格.
翻譯初衷:
規(guī)則的作用就是避免混亂. 但規(guī)則本身一定要權(quán)威, 有說服力, 并且是理性的. 我們所見過的大部分編程規(guī)范, 其內(nèi)容或不夠嚴(yán)謹(jǐn), 或闡述過于簡單, 或帶有一定的武斷性.
Google 保持其一貫的嚴(yán)謹(jǐn)精神, 5 萬漢字的指南涉及廣泛, 論證嚴(yán)密. 我們翻譯該系列指南的主因也正是其嚴(yán)謹(jǐn)性. 嚴(yán)謹(jǐn)意味著指南的價值不僅僅局限于它羅列出的規(guī)范, 更具參考意義的是它為了列出規(guī)范而做的謹(jǐn)慎權(quán)衡過程.
指南不僅列出你要怎么做, 還告訴你為什么要這么做, 哪些情況下可以不這么做, 以及如何權(quán)衡其利弊. 其他團(tuán)隊未必要完全遵照指南亦步亦趨, 如前面所說, 這份指南是 Google 根據(jù)自身實(shí)際情況打造的, 適用于其主導(dǎo)的開源項目. 其他團(tuán)隊可以參照該指南, 或從中汲取靈感, 建立適合自身實(shí)際情況的規(guī)范.
我們在翻譯的過程中, 收獲頗多. 希望本系列指南中文版對你同樣能有所幫助.
我們翻譯時也是盡力保持嚴(yán)謹(jǐn), 但水平所限, bug 在所難免. 有任何意見或建議, 可與我們?nèi)〉寐?lián)系.
中文版和英文版一樣, 使用ArtisticLicense/GPL開源許可.
中文版修訂歷史:
2015-08 : 熱心的清華大學(xué)同學(xué) @lilinsanity 完善了「類」章節(jié)以及其它一些小章節(jié)。至此,對 Google CPP Style Guide 4.45 的翻譯正式竣工。
2015-07 4.45 : acgtyrant 為了學(xué)習(xí) C++ 的規(guī)范,順便重新翻譯了本 C++ 風(fēng)格指南,特別是 C++11 的全新內(nèi)容。排版大幅度優(yōu)化,翻譯措辭更地道,添加了新譯者筆記。Google 總部 C++ 工程師 innocentim, 清華大學(xué)不愿意透露姓名的唐馬儒先生,大阪大學(xué)大學(xué)院情報科學(xué)研究科計算機(jī)科學(xué)專攻博士 farseerfc 和其它 Arch Linux 中文社區(qū)眾幫了譯者不少忙,謝謝他們。因為 C++ Primer 尚未完全入門,暫時沒有翻譯「類」章節(jié)和其它一些小章節(jié)。
2009-06 3.133 : YuleFox 的 1.0 版已經(jīng)相當(dāng)完善, 但原版在近一年的時間里, 其規(guī)范也發(fā)生了一些變化.
Yang.Y 與 YuleFox 一拍即合, 以項目的形式來延續(xù)中文版 :Google 開源項目風(fēng)格指南 - 中文版項目.
主要變化是同步到 3.133 最新英文版本, 做部分勘誤和改善可讀性方面的修改, 并改進(jìn)排版效果. Yang.Y 重新翻修, YuleFox 做后續(xù)評審.
2008-07 1.0 : 出自YuleFox 的 Blog, 很多地方摘錄的也是該版本.
0.2 背景
C++ 是 Google 大部分開源項目的主要編程語言. 正如每個 C++ 程序員都知道的, C++ 有很多強(qiáng)大的特性, 但這種強(qiáng)大不可避免的導(dǎo)致它走向復(fù)雜,使代碼更容易產(chǎn)生 bug, 難以閱讀和維護(hù).
本指南的目的是通過詳細(xì)闡述 C++ 注意事項來駕馭其復(fù)雜性. 這些規(guī)則在保證代碼易于管理的同時, 也能高效使用 C++ 的語言特性.
風(fēng)格, 亦被稱作可讀性, 也就是指導(dǎo) C++ 編程的約定. 使用術(shù)語 “風(fēng)格” 有些用詞不當(dāng), 因為這些習(xí)慣遠(yuǎn)不止源代碼文件格式化這么簡單.
使代碼易于管理的方法之一是加強(qiáng)代碼一致性. 讓任何程序員都可以快速讀懂你的代碼這點(diǎn)非常重要. 保持統(tǒng)一編程風(fēng)格并遵守約定意味著可以很容易根據(jù) “模式匹配” 規(guī)則來推斷各種標(biāo)識符的含義. 創(chuàng)建通用, 必需的習(xí)慣用語和模式可以使代碼更容易理解. 在一些情況下可能有充分的理由改變某些編程風(fēng)格, 但我們還是應(yīng)該遵循一致性原則,盡量不這么做.
本指南的另一個觀點(diǎn)是 C++ 特性的臃腫. C++ 是一門包含大量高級特性的龐大語言. 某些情況下, 我們會限制甚至禁止使用某些特性. 這么做是為了保持代碼清爽, 避免這些特性可能導(dǎo)致的各種問題. 指南中列舉了這類特性, 并解釋為什么這些特性被限制使用.
Google 主導(dǎo)的開源項目均符合本指南的規(guī)定.
注意: 本指南并非 C++ 教程, 我們假定讀者已經(jīng)對 C++ 非常熟悉.
1. 頭文件
通常每一個.cc文件都有一個對應(yīng)的.h文件. 也有一些常見例外, 如單元測試代碼和只包含main()函數(shù)的.cc文件.
正確使用頭文件可令代碼在可讀性、文件大小和性能上大為改觀.
下面的規(guī)則將引導(dǎo)你規(guī)避使用頭文件時的各種陷阱.
1.1. Self-contained 頭文件
Tip
頭文件應(yīng)該能夠自給自足(self-contained,也就是可以作為第一個頭文件被引入),以.h結(jié)尾。至于用來插入文本的文件,說到底它們并不是頭文件,所以應(yīng)以.inc結(jié)尾。不允許分離出-inl.h頭文件的做法.
所有頭文件要能夠自給自足。換言之,用戶和重構(gòu)工具不需要為特別場合而包含額外的頭文件。詳言之,一個頭文件要有1.2. #define 保護(hù),統(tǒng)統(tǒng)包含它所需要的其它頭文件,也不要求定義任何特別 symbols.
不過有一個例外,即一個文件并不是 self-contained 的,而是作為文本插入到代碼某處。或者,文件內(nèi)容實(shí)際上是其它頭文件的特定平臺(platform-specific)擴(kuò)展部分。這些文件就要用.inc文件擴(kuò)展名。
如果.h文件聲明了一個模板或內(nèi)聯(lián)函數(shù),同時也在該文件加以定義。凡是有用到這些的.cc文件,就得統(tǒng)統(tǒng)包含該頭文件,否則程序可能會在構(gòu)建中鏈接失敗。不要把這些定義放到分離的-inl.h文件里(譯者注:過去該規(guī)范曾提倡把定義放到 -inl.h 里過)。
有個例外:如果某函數(shù)模板為所有相關(guān)模板參數(shù)顯式實(shí)例化,或本身就是某類的一個私有成員,那么它就只能定義在實(shí)例化該模板的.cc文件里。
1.2. #define 保護(hù)
Tip
所有頭文件都應(yīng)該使用#define來防止頭文件被多重包含, 命名格式當(dāng)是:
為保證唯一性, 頭文件的命名應(yīng)該基于所在項目源代碼樹的全路徑. 例如, 項目foo中的頭文件foo/src/bar/baz.h可按如下方式保護(hù):
#ifndef FOO_BAR_BAZ_H_#define FOO_BAR_BAZ_H_...#endif // FOO_BAR_BAZ_H_
1.3. 前置聲明
Tip
盡可能地避免使用前置聲明。使用#include包含需要的頭文件即可。
定義:
所謂「前置聲明」(forward declaration)是類、函數(shù)和模板的純粹聲明,沒伴隨著其定義.
優(yōu)點(diǎn):
前置聲明能夠節(jié)省編譯時間,多余的#include會迫使編譯器展開更多的文件,處理更多的輸入。
前置聲明能夠節(jié)省不必要的重新編譯的時間。#include使代碼因為頭文件中無關(guān)的改動而被重新編譯多次。
缺點(diǎn):
前置聲明隱藏了依賴關(guān)系,頭文件改動時,用戶的代碼會跳過必要的重新編譯過程。
前置聲明可能會被庫的后續(xù)更改所破壞。前置聲明函數(shù)或模板有時會妨礙頭文件開發(fā)者變動其 API. 例如擴(kuò)大形參類型,加個自帶默認(rèn)參數(shù)的模板形參等等。
前置聲明來自命名空間std::的 symbol 時,其行為未定義。
很難判斷什么時候該用前置聲明,什么時候該用#include。極端情況下,用前置聲明代替includes甚至都會暗暗地改變代碼的含義:
// b.h:struct B {};struct D : B {}// good_user.cc:#include "b.h"void f(B*);void f(void*);void test(D* x) { f(x); } // calls f(B*)
如果#include被B和D的前置聲明替代,test()就會調(diào)用f(void*).
前置聲明了不少來自頭文件的 symbol 時,就會比單單一行的include冗長。
僅僅為了能前置聲明而重構(gòu)代碼(比如用指針成員代替對象成員)會使代碼變得更慢更復(fù)雜.
結(jié)論:
盡量避免前置聲明那些定義在其他項目中的實(shí)體.
函數(shù):總是使用#include.
類模板:優(yōu)先使用#include.
至于什么時候包含頭文件,參見1.5. #include 的路徑及順序。
1.4. 內(nèi)聯(lián)函數(shù)
Tip
只有當(dāng)函數(shù)只有 10 行甚至更少時才將其定義為內(nèi)聯(lián)函數(shù).
定義:
當(dāng)函數(shù)被聲明為內(nèi)聯(lián)函數(shù)之后, 編譯器會將其內(nèi)聯(lián)展開, 而不是按通常的函數(shù)調(diào)用機(jī)制進(jìn)行調(diào)用.
優(yōu)點(diǎn):
只要內(nèi)聯(lián)的函數(shù)體較小, 內(nèi)聯(lián)該函數(shù)可以令目標(biāo)代碼更加高效. 對于存取函數(shù)以及其它函數(shù)體比較短, 性能關(guān)鍵的函數(shù), 鼓勵使用內(nèi)聯(lián).
缺點(diǎn):
濫用內(nèi)聯(lián)將導(dǎo)致程序變得更慢. 內(nèi)聯(lián)可能使目標(biāo)代碼量或增或減, 這取決于內(nèi)聯(lián)函數(shù)的大小. 內(nèi)聯(lián)非常短小的存取函數(shù)通常會減少代碼大小, 但內(nèi)聯(lián)一個相當(dāng)大的函數(shù)將戲劇性的增加代碼大小. 現(xiàn)代處理器由于更好的利用了指令緩存, 小巧的代碼往往執(zhí)行更快。
結(jié)論:
一個較為合理的經(jīng)驗準(zhǔn)則是, 不要內(nèi)聯(lián)超過 10 行的函數(shù). 謹(jǐn)慎對待析構(gòu)函數(shù), 析構(gòu)函數(shù)往往比其表面看起來要更長, 因為有隱含的成員和基類析構(gòu)函數(shù)被調(diào)用!
另一個實(shí)用的經(jīng)驗準(zhǔn)則: 內(nèi)聯(lián)那些包含循環(huán)或switch語句的函數(shù)常常是得不償失 (除非在大多數(shù)情況下, 這些循環(huán)或switch語句從不被執(zhí)行).
有些函數(shù)即使聲明為內(nèi)聯(lián)的也不一定會被編譯器內(nèi)聯(lián), 這點(diǎn)很重要; 比如虛函數(shù)和遞歸函數(shù)就不會被正常內(nèi)聯(lián). 通常, 遞歸函數(shù)不應(yīng)該聲明成內(nèi)聯(lián)函數(shù).(YuleFox 注: 遞歸調(diào)用堆棧的展開并不像循環(huán)那么簡單, 比如遞歸層數(shù)在編譯時可能是未知的, 大多數(shù)編譯器都不支持內(nèi)聯(lián)遞歸函數(shù)). 虛函數(shù)內(nèi)聯(lián)的主要原因則是想把它的函數(shù)體放在類定義內(nèi), 為了圖個方便, 抑或是當(dāng)作文檔描述其行為, 比如精短的存取函數(shù).
1.5.#include的路徑及順序
Tip
使用標(biāo)準(zhǔn)的頭文件包含順序可增強(qiáng)可讀性, 避免隱藏依賴: 相關(guān)頭文件, C 庫, C++ 庫, 其他庫的.h, 本項目內(nèi)的.h.
項目內(nèi)頭文件應(yīng)按照項目源代碼目錄樹結(jié)構(gòu)排列, 避免使用 UNIX 特殊的快捷目錄.(當(dāng)前目錄) 或..(上級目錄). 例如,google-awesome-project/src/base/logging.h應(yīng)該按如下方式包含:
#include "base/logging.h"
又如,dir/foo.cc或dir/foo_test.cc的主要作用是實(shí)現(xiàn)或測試dir2/foo2.h的功能,foo.cc中包含頭文件的次序如下:
dir2/foo2.h(優(yōu)先位置, 詳情如下)
C 系統(tǒng)文件
C++ 系統(tǒng)文件
其他庫的.h文件
本項目內(nèi).h文件
這種優(yōu)先的順序排序保證當(dāng)dir2/foo2.h遺漏某些必要的庫時,dir/foo.cc或dir/foo_test.cc的構(gòu)建會立刻中止。因此這一條規(guī)則保證維護(hù)這些文件的人們首先看到構(gòu)建中止的消息而不是維護(hù)其他包的人們。
dir/foo.cc和dir2/foo2.h通常位于同一目錄下 (如base/basictypes_unittest.cc和base/basictypes.h), 但也可以放在不同目錄下.
按字母順序分別對每種類型的頭文件進(jìn)行二次排序是不錯的主意。注意較老的代碼可不符合這條規(guī)則,要在方便的時候改正它們。
您所依賴的符號 (symbols) 被哪些頭文件所定義,您就應(yīng)該包含(include)哪些頭文件,前置聲明(forward declarations) 情況除外。比如您要用到bar.h中的某個符號, 哪怕您所包含的foo.h已經(jīng)包含了bar.h, 也照樣得包含bar.h, 除非foo.h有明確說明它會自動向您提供bar.h中的 symbol. 不過,凡是 cc 文件所對應(yīng)的「相關(guān)頭文件」已經(jīng)包含的,就不用再重復(fù)包含進(jìn)其 cc 文件里面了,就像foo.cc只包含foo.h就夠了,不用再管后者所包含的其它內(nèi)容。
舉例來說,google-awesome-project/src/foo/internal/fooserver.cc的包含次序如下:
#include "foo/public/fooserver.h" // 優(yōu)先位置#include
例外:
有時,平臺特定(system-specific)代碼需要條件編譯(conditional includes),這些代碼可以放到其它 includes 之后。當(dāng)然,您的平臺特定代碼也要夠簡練且獨(dú)立,比如:
#include "foo/public/fooserver.h"#include "base/port.h" // For LANG_CXX11.#ifdef LANG_CXX11#include
譯者 (YuleFox) 筆記
避免多重包含是學(xué)編程時最基本的要求;
前置聲明是為了降低編譯依賴,防止修改一個頭文件引發(fā)多米諾效應(yīng);
內(nèi)聯(lián)函數(shù)的合理使用可提高代碼執(zhí)行效率;
-inl.h可提高代碼可讀性 (一般用不到吧:D);
標(biāo)準(zhǔn)化函數(shù)參數(shù)順序可以提高可讀性和易維護(hù)性 (對函數(shù)參數(shù)的堆棧空間有輕微影響, 我以前大多是相同類型放在一起);
包含文件的名稱使用.和..雖然方便卻易混亂, 使用比較完整的項目路徑看上去很清晰, 很條理, 包含文件的次序除了美觀之外, 最重要的是可以減少隱藏依賴, 使每個頭文件在 “最需要編譯” (對應(yīng)源文件處 :D) 的地方編譯, 有人提出庫文件放在最后, 這樣出錯先是項目內(nèi)的文件, 頭文件都放在對應(yīng)源文件的最前面, 這一點(diǎn)足以保證內(nèi)部錯誤的及時發(fā)現(xiàn)了.
譯者(acgtyrant)筆記
原來還真有項目用#includes來插入文本,且其文件擴(kuò)展名.inc看上去也很科學(xué)。
Google 已經(jīng)不再提倡-inl.h用法。
注意,前置聲明的類是不完全類型(incomplete type),我們只能定義指向該類型的指針或引用,或者聲明(但不能定義)以不完全類型作為參數(shù)或者返回類型的函數(shù)。畢竟編譯器不知道不完全類型的定義,我們不能創(chuàng)建其類的任何對象,也不能聲明成類內(nèi)部的數(shù)據(jù)成員。
類內(nèi)部的函數(shù)一般會自動內(nèi)聯(lián)。所以某函數(shù)一旦不需要內(nèi)聯(lián),其定義就不要再放在頭文件里,而是放到對應(yīng)的.cc文件里。這樣可以保持頭文件的類相當(dāng)精煉,也很好地貫徹了聲明與定義分離的原則。
在#include中插入空行以分割相關(guān)頭文件, C 庫, C++ 庫, 其他庫的.h和本項目內(nèi)的.h是個好習(xí)慣。
-
Google
+關(guān)注
關(guān)注
5文章
1765瀏覽量
57530 -
編程
+關(guān)注
關(guān)注
88文章
3616瀏覽量
93734 -
C++
+關(guān)注
關(guān)注
22文章
2108瀏覽量
73651
原文標(biāo)題:Google C++ 編程規(guī)范 - 1
文章出處:【微信號:C_Expert,微信公眾號:C語言專家集中營】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。
發(fā)布評論請先 登錄
相關(guān)推薦
評論