[譯]Google C++編程風格指南(一)

  • 背景

Google的開源項目大多使用C++開發。每個C++程序員也都知道,C++具備不少強大的語言特性,但這種強大不可避免的致使它的複雜,這種複雜會使得代碼更易於出現bug、難於閱讀和維護。程序員

本指南的目的是經過詳細闡述在C++編碼時要怎樣寫、不要怎樣寫來規避其複雜性。這些規則可在容許代碼有效使用C++語言特性的同時使其易於管理。編程

風格,也被視爲可讀性,主要指稱管理C++代碼的習慣。使用術語風格有點用詞不當,由於這些習慣遠不止源代碼文件格式這麼簡單。緩存

使代碼易於管理的方法之一是加強代碼一致性,讓別人能夠讀懂你的代碼是很重要的,保持統一編程風格意味着能夠輕鬆根據「模式匹配」規則推斷各類符號的含義。建立通用的、必需的習慣用語和模式可使代碼更加容易理解,在某些狀況下改變一些編程風格可能會是好的選擇,但咱們仍是應該遵循一致性原則,儘可能不這樣去作。函數

本指南的另外一個觀點是C++特性的臃腫。C++是一門包含大量高級特性的巨型語言,某些狀況下,咱們會限制甚至禁止使用某些特性使代碼簡化,避免可能致使的各類問題,指南中列舉了這類特性,並解釋說爲何這些特性是被限制使用的。性能

由Google開發的開源項目將遵守本指南約定。單元測試

注意:本指南並不是C++教程,咱們假定讀者已經對C++很是熟悉。測試

  • 頭文件

一般,每個.cc文件(C++的源文件)都有一個對應的.h文件(頭文件),也有一些例外,如單元測試代碼和只包含main()的.cc文件。google

正確使用頭文件可令代碼在可讀性、文件大小和性能上大爲改觀。編碼

下面的規則將引導你規避使用頭文件時的各類麻煩。spa

1. #define的保護

全部頭文件都應該使用#define防止頭文件被多重包含(multiple inclusion),命名格式當是: ___H_

爲保證惟一性,頭文件的命名應基於其所在項目源代碼樹的全路徑。例如,項目foo中的頭文件foo/src/bar/baz.h按以下方式保護:

#ifndef FOO_BAR_BAZ_H_
#define FOO_BAR_BAZ_H_
...
#endif // FOO_BAR_BAZ_H_

2. 頭文件依賴

使用前置聲明(forward declarations)儘可能減小.h文件中#include的數量。

當一個頭文件被包含的同時也引入了一項新的依賴(dependency),只要該頭文件被修改,代碼就要從新編譯。若是你的頭文件包含了其餘頭文件,這些頭文件的任何改變也將致使那些包含了你的頭文件的代碼從新編譯。所以,咱們寧肯儘可能少包含頭文件,尤爲是那些包含在其餘頭文件中的。

使用前置聲明能夠顯著減小須要包含的頭文件數量。舉例說明:頭文件中用到類File,但不須要訪問File的聲明,則頭文件中只需前置聲明class File;無需#include "file/base/file.h"

在頭文件如何作到使用類Foo而無需訪問類的定義?

1) 將數據成員類型聲明爲Foo *或Foo &;

2) 參數、返回值類型爲Foo的函數只是聲明(但不定義實現);

3) 靜態數據成員的類型能夠被聲明爲Foo,由於靜態數據成員的定義在類定義以外。

另外一方面,若是你的類是Foo的子類,或者含有類型爲Foo的非靜態數據成員,則必須爲之包含頭文件。

有時,使用指針成員(pointer members,若是是scoped_ptr更好)替代對象成員(object members)的確更有意義。然而,這樣的作法會下降代碼可讀性及執行效率。若是僅僅爲了少包含頭文件,仍是不要這樣替代的好。

固然,.cc文件不管如何都須要所使用類的定義部分,天然也就會包含若干頭文件。

譯者注:能依賴聲明的就不要依賴定義。

3. 內聯函數

只有當函數只有10行甚至更少時纔會將其定義爲內聯函數(inline function)

定義(Definition):當函數被聲明爲內聯函數以後,編譯器可能會將其內聯展開,無需按一般的函數調用機制調用內聯函數。

優勢:當函數體比較小的時候,內聯該函數能夠令目標代碼更加高效。對於存取函數(accessor、mutator)以及其餘一些比較短的關鍵執行函數。

缺點:濫用內聯將致使程序變慢,內聯有多是目標代碼量或增或減,這取決於被內聯的函數的大小。內聯較短小的存取函數一般會減小代碼量,但內聯一個很大的函數(譯者注:若是編譯器容許的話)將戲劇性的增長代碼量。在現代處理器上,因爲更好的利用指令緩存(instruction cache),小巧的代碼每每執行更快。

結論:一個比較得當的處理規則是,不要內聯超過10行的函數。對於析構函數應慎重對待,析構函數每每比其表面看起來要長,由於有一些隱式成員和基類析構函數(若是有的話)被調用!

另外一有用的處理規則:內聯那些包含循環或switch語句的函數是得不償失的,除非在大多數狀況下,這些循環或switch語句從不執行。

重要的是,虛函數和遞歸函數即便被聲明爲內聯的也不必定就是內聯函數。一般,遞歸函數不該該被聲明爲內聯的(譯者注:遞歸調用堆棧的展開並不像循環那麼簡單,好比遞歸層數在編譯時多是未知的,大多數編譯器都不支持內聯遞歸函數)。析構函數內聯的主要緣由是其定義在類的定義中,爲了方便抑或是對其行爲給出文檔。

4. -inl.h文件

複雜的內聯函數的定義,應放在後綴名爲-inl.h的頭文件中。

在頭文件中給出內聯函數的定義,可令編譯器將其在調用處內聯展開。然而,實現代碼應徹底放到.cc文件中,咱們不但願.h文件中出現太多實現代碼,除非這樣作在可讀性和效率上有明顯優點。

若是內聯函數的定義比較短小、邏輯比較簡單,其實現代碼能夠放在.h文件中。例如,存取函數的實現理所固然都放在類定義中。出於實現和調用的方便,較複雜的內聯函數也能夠放到.h文件中,若是你以爲這樣會使頭文件顯得笨重,還能夠將其分離到單獨的-inl.h中。這樣即把實現和類定義分離開來,當須要時包含實現所在的-inl.h便可。

-inl.h文件還可用於函數模板的定義,從而使得模板定義可讀性加強。

要提醒的一點是,-inl.h和其餘頭文件同樣,也須要#define保護。

5. 函數參數順序(Function Parameter Ordering)

定義函數時,參數順序爲:輸入參數在前,輸出參數在後。

C/C++函數參數分爲輸入參數和輸出參數兩種,有時輸入參數也會輸出(譯者注:值被修改時)。輸入參數通常傳值或常數引用(const references),輸出參數或輸入/輸出參數爲很是數指針(non-const pointers)。對參數排序時,將全部輸入參數置於輸出參數以前。不要僅僅由於是新添加的參數,就將其置於最後,而應該依然置於輸出參數以前。

這一點並非必須遵循的規則,輸入/輸出兩用參數(一般是類/結構體變量)混在其中,會使得規則難以遵循。

6. 包含文件的名稱及次序

將包含次序標準化可加強可讀性、避免隱藏依賴(hidden dependencies,譯者注:隱藏依賴主要是指包含的文件中編譯時),次序以下:C庫、C++庫、其餘庫的.h、項目內的.h。

項目內頭文件應按照項目源代碼目錄樹結構排列,而且避免使用UNIX文件路徑.(當前目錄)和..(父目錄)。例如,google-awesome-project/src/base/logging.h應像這樣被包含:

#include "base/logging.h"

dir/foo.cc的主要做用是執行或測試dir2/foo2.h的功能,foo.cc中包含頭文件的次序以下:

    dir2/foo2.h(優先位置,詳情以下)
    C系統文件
    C++系統文件
    其餘庫頭文件
    本項目內頭文件

這種排序方式可有效減小隱藏依賴,咱們但願每個頭文件獨立編譯。最簡單的實現方式是將其做爲第一個.h文件包含在對應的.cc中。

dir/foo.cc和dir2/foo2.h一般位於相同目錄下(像base/basictypes_unittest.cc和base/basictypes.h),但也可在不一樣目錄下。

相同目錄下頭文件按字母序是不錯的選擇。

舉例來講,google-awesome-project/src/foo/internal/fooserver.cc的包含次序以下:

#include "foo/public/fooserver.h"  // 優先位置

#include
#include

#include
#include

#include "base/basictypes.h"
#include "base/commandlineflags.h"
#include "foo/public/bar.h"

______________________________________

譯者:英語不太好,翻譯的也就不太好。這一篇主要提到的是頭文件的一些規則,總結一下:

1. 避免多重包含是學編程時最基本的要求;

2. 前置聲明是爲了下降編譯依賴,防止修改一個頭文件引起多米諾效應;

3. 內聯函數的合理使用可提升代碼執行效率;

4. -inl.h可提升代碼可讀性(通常用不到吧:D);

5. 標準化函數參數順序能夠提升可讀性和易維護性(對函數參數的堆棧空間有輕微影響,我之前大可能是相同類型放在一塊兒);

6. 包含文件的名稱使用.和..雖然方便卻易混亂,使用比較完整的項目路徑看上去很清晰、很條理,包含文件的次序除了美觀以外,最重要的是能夠減小隱藏依賴,使每一個頭文件在「最須要編譯」(對應源文件處:D)的地方編譯,有人提出庫文件放在最後,這樣出錯先是項目內的文件,頭文件都放在對應源文件的最前面,這一點足以保證內部錯誤的及時發現了。

相關文章
相關標籤/搜索