Google C++程式設計風格指南

yangdelong發表於2009-10-10

http://www.kuqin.com/language/20080712/11058.html

 

本指南的目的是通過詳細闡述在C++編碼時要怎樣寫、不要怎樣寫來規避其複雜性。本指南的另一個觀點是C++特性的臃腫。C++是一門包含大量高階特性的巨型語言,某些情況下,我們會限制甚至禁止使用某些特性使程式碼簡化,避免可能導致的各種問題

 

系列文章目錄:
Google C++程式設計風格指南(一):標頭檔案       【本文】
Google C++程式設計風格指南(二):作用域
Google C++程式設計風格指南(三):C++ 類
Google C++程式設計風格指南(四):智慧指標和其他C++特性
Google C++程式設計風格指南(五):命名約定
Google C++程式設計風格指南(六):程式碼註釋
Google C++程式設計風格指南(七):格式
Google C++程式設計風格指南(八):規則之例外

 

  • 背景

Google的開源專案大多使用C++開發。每一個C++程式設計師也都知道,C++具有很多強大的語言特性,但這種強大不可避免的導致它的複雜,這種複雜會使得程式碼更易於出現bug、難於閱讀和維護。

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

風格,也被視為可讀性,主要指稱管理C++程式碼的習慣。使用術語風格有點用詞不當,因為這些習慣遠不止原始碼檔案格式這麼簡單。

使程式碼易於管理的方法之一是增強程式碼一致性,讓別人可以讀懂你的程式碼是很重要的,保持統一程式設計風格意味著可以輕鬆根據“模式匹配”規則推斷各種符號的含義。建立通用的、必需的習慣用語和模式可以使程式碼更加容易理解,在某些情況下改變一些程式設計風格可能會是好的選擇,但我們還是應該遵循一致性原則,儘量不這樣去做。

本指南的另一個觀點是C++特性的臃腫。C++是一門包含大量高階特性的巨型語言,某些情況下,我們會限制甚至禁止使用某些特性使程式碼簡化,避免可能導致的各種問題,指南中列舉了這類特性,並解釋說為什麼這些特性是被限制使用的。

由Google開發的開源專案將遵照本指南約定。

注意:本指南並非C++教程,我們假定讀者已經對C++非常熟悉。

  • 標頭檔案

通常,每一個.cc檔案(C++的原始檔)都有一個對應的.h檔案(標頭檔案),也有一些例外,如單元測試程式碼和只包含main()的.cc檔案。

正確使用標頭檔案可令程式碼在可讀性、檔案大小和效能上大為改觀。

下面的規則將引導你規避使用標頭檔案時的各種麻煩。

1. #define的保護

所有標頭檔案都應該使用#define防止標頭檔案被多重包含(multiple inclusion),命名格式當是:<PROJECT>_<PATH>_<FILE>_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 <sys/types.h>
#include <unistd.h>

#include <hash_map>
#include <vector>

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

______________________________________

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

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

2. 前置宣告是為了降低編譯依賴,防止修改一個標頭檔案引發多米諾效應;

3. 行內函數的合理使用可提高程式碼執行效率;

4. -inl.h可提高程式碼可讀性(一般用不到吧:D);

5. 標準化函式引數順序可以提高可讀性和易維護性(對函式引數的堆疊空間有輕微影響,我以前大多是相同型別放在一起);

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

來自:http://www.cppblog.com/Fox/archive/2008/07/10/55818.html
原文:Google C++ Style Guide

相關閱讀:C++程式碼風格(谷歌版)

相關文章