找回密碼
 立即注冊(cè)

QQ登錄

只需一步,快速開始

搜索
查看: 2590|回復(fù): 0
打印 上一主題 下一主題
收起左側(cè)

C++編程風(fēng)格指南(共58頁pdf下載)

[復(fù)制鏈接]
跳轉(zhuǎn)到指定樓層
樓主
不錯(cuò)的資料,對(duì)寫程序有幫助


背景

公司的開源項(xiàng)目大多使用C++開發(fā)。每一個(gè)C++程序員也都知道,C++具有很多強(qiáng)大的語言特性,但這種強(qiáng)大不可避免的導(dǎo)致它的復(fù)雜,這種復(fù)雜會(huì)使得代碼更易于出現(xiàn)bug、難于閱讀和維護(hù)。

本指南的目的是通過詳細(xì)闡述在C++編碼時(shí)要怎樣寫、不要怎樣寫來規(guī)避其復(fù)雜性。這些規(guī)則可在允許代碼有效使用C++語言特性的同時(shí)使其易于管理。

風(fēng)格,也被視為可讀性,主要指稱管理C++代碼的習(xí)慣。使用術(shù)語風(fēng)格有點(diǎn)用詞不當(dāng),因?yàn)檫@些習(xí)慣遠(yuǎn)不止源代碼文件格式這么簡(jiǎn)單。

使代碼易于管理的方法之一是增強(qiáng)代碼一致性,讓別人可以讀懂你的代碼是很重要的,保持統(tǒng)一編程風(fēng)格意味著可以輕松根據(jù)“模式匹配”規(guī)則推斷各種符號(hào)的含義。創(chuàng)建通用的、必需的習(xí)慣用語和模式可以使代碼更加容易理解,在某些情況下改變一些編程風(fēng)格可能會(huì)是好的選擇,但我們還是應(yīng)該遵循一致性原則,盡量不這樣去做。

本指南的另一個(gè)觀點(diǎn)是C++特性的臃腫。C++是一門包含大量高級(jí)特性的巨型語言,某些情況下,我們會(huì)限制甚至禁止使用某些特性使代碼簡(jiǎn)化,避免可能導(dǎo)致的各種問題,指南中列舉了這類特性,并解釋說為什么這些特性是被限制使用的。

由公司開發(fā)的開源項(xiàng)目將遵照本指南約定。

注意:本指南并非C++教程,我們假定讀者已經(jīng)對(duì)C++非常熟悉。

頭文件

通常,每一個(gè).cc文件(C++的源文件)都有一個(gè)對(duì)應(yīng)的.h文件(頭文件),也有一些例外,如單元測(cè)試代碼和只包含main()的.cc文件。

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

下面的規(guī)則將引導(dǎo)你規(guī)避使用頭文件時(shí)的各種麻煩。

1. #define的保護(hù)

所有頭文件都應(yīng)該使用#define防止頭文件被多重包含(multipleinclusion),命名格式當(dāng)是:<PROJECT>_<PATH>_<FILE>_H_

為保證唯一性,頭文件的命名應(yīng)基于其所在項(xiàng)目源代碼樹的全路徑。例如,項(xiàng)目foo中的頭文件foo/src/bar/baz.h按如下方式保護(hù):

#ifndef FOO_BAR_BAZ_H_

#define FOO_BAR_BAZ_H_

...

#endif // FOO_BAR_BAZ_H_

2. 頭文件依賴

使用前置聲明(forwarddeclarations)盡量減少.h文件中#include的數(shù)量。

當(dāng)一個(gè)頭文件被包含的同時(shí)也引入了一項(xiàng)新的依賴(dependency),只要該頭文件被修改,代碼就要重新編譯。如果你的頭文件包含了其他頭文件,這些頭文件的任何改變也將導(dǎo)致那些包含了你的頭文件的代碼重新編譯。因此,我們寧可盡量少包含頭文件,尤其是那些包含在其他頭文件中的。

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

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

1) 將數(shù)據(jù)成員類型聲明為Foo *或Foo &;

2) 參數(shù)、返回值類型為Foo的函數(shù)只是聲明(但不定義實(shí)現(xiàn));

3) 靜態(tài)數(shù)據(jù)成員的類型可以被聲明為Foo,因?yàn)殪o態(tài)數(shù)據(jù)成員的定義在類定義之外。

另一方面,如果你的類是Foo的子類,或者含有類型為Foo的非靜態(tài)數(shù)據(jù)成員,則必須為之包含頭文件。

有時(shí),使用指針成員(pointer members,如果是scoped_ptr更好)替代對(duì)象成員(object members)的確更有意義。然而,這樣的做法會(huì)降低代碼可讀性及執(zhí)行效率。如果僅僅為了少包含頭文件,還是不要這樣替代的好。

當(dāng)然,.cc文件無論如何都需要所使用類的定義部分,自然也就會(huì)包含若干頭文件。

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

3. 內(nèi)聯(lián)函數(shù)

只有當(dāng)函數(shù)只有10行甚至更少時(shí)才會(huì)將其定義為內(nèi)聯(lián)函數(shù)(inline function)。

定義(Definition):當(dāng)函數(shù)被聲明為內(nèi)聯(lián)函數(shù)之后,編譯器可能會(huì)將其內(nèi)聯(lián)展開,無需按通常的函數(shù)調(diào)用機(jī)制調(diào)用內(nèi)聯(lián)函數(shù)。

優(yōu)點(diǎn):當(dāng)函數(shù)體比較小的時(shí)候,內(nèi)聯(lián)該函數(shù)可以令目標(biāo)代碼更加高效。對(duì)于存取函數(shù)(accessor、mutator)以及其他一些比較短的關(guān)鍵執(zhí)行函數(shù)。

缺點(diǎn):濫用內(nèi)聯(lián)將導(dǎo)致程序變慢,內(nèi)聯(lián)有可能是目標(biāo)代碼量或增或減,這取決于被內(nèi)聯(lián)的函數(shù)的大小。內(nèi)聯(lián)較短小的存取函數(shù)通常會(huì)減少代碼量,但內(nèi)聯(lián)一個(gè)很大的函數(shù)(譯者注:如果編譯器允許的話)將戲劇性的增加代碼量。在現(xiàn)代處理器上,由于更好的利用指令緩存(instruction cache),小巧的代碼往往執(zhí)行更快。

結(jié)論:一個(gè)比較得當(dāng)?shù)奶幚硪?guī)則是,不要內(nèi)聯(lián)超過10行的函數(shù)。對(duì)于析構(gòu)函數(shù)應(yīng)慎重對(duì)待,析構(gòu)函數(shù)往往比其表面看起來要長(zhǎng),因?yàn)橛幸恍╇[式成員和基類析構(gòu)函數(shù)(如果有的話)被調(diào)用!

另一有用的處理規(guī)則:內(nèi)聯(lián)那些包含循環(huán)或switch語句的函數(shù)是得不償失的,除非在大多數(shù)情況下,這些循環(huán)或switch語句從不執(zhí)行。

重要的是,虛函數(shù)和遞歸函數(shù)即使被聲明為內(nèi)聯(lián)的也不一定就是內(nèi)聯(lián)函數(shù)。通常,遞歸函數(shù)不應(yīng)該被聲明為內(nèi)聯(lián)的(譯者注:遞歸調(diào)用堆棧的展開并不像循環(huán)那么簡(jiǎn)單,比如遞歸層數(shù)在編譯時(shí)可能是未知的,大多數(shù)編譯器都不支持內(nèi)聯(lián)遞歸函數(shù))。析構(gòu)函數(shù)內(nèi)聯(lián)的主要原因是其定義在類的定義中,為了方便抑或是對(duì)其行為給出文檔。

4. -inl.h文件

復(fù)雜的內(nèi)聯(lián)函數(shù)的定義,應(yīng)放在后綴名為-inl.h的頭文件中。

在頭文件中給出內(nèi)聯(lián)函數(shù)的定義,可令編譯器將其在調(diào)用處內(nèi)聯(lián)展開。然而,實(shí)現(xiàn)代碼應(yīng)完全放到.cc文件中,我們不希望.h文件中出現(xiàn)太多實(shí)現(xiàn)代碼,除非這樣做在可讀性和效率上有明顯優(yōu)勢(shì)。

如果內(nèi)聯(lián)函數(shù)的定義比較短小、邏輯比較簡(jiǎn)單,其實(shí)現(xiàn)代碼可以放在.h文件中。例如,存取函數(shù)的實(shí)現(xiàn)理所當(dāng)然都放在類定義中。出于實(shí)現(xiàn)和調(diào)用的方便,較復(fù)雜的內(nèi)聯(lián)函數(shù)也可以放到.h文件中,如果你覺得這樣會(huì)使頭文件顯得笨重,還可以將其分離到單獨(dú)的-inl.h中。這樣即把實(shí)現(xiàn)和類定義分離開來,當(dāng)需要時(shí)包含實(shí)現(xiàn)所在的-inl.h即可。

-inl.h文件還可用于函數(shù)模板的定義,從而使得模板定義可讀性增強(qiáng)。

要提醒的一點(diǎn)是,-inl.h和其他頭文件一樣,也需要#define保護(hù)。

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

定義函數(shù)時(shí),參數(shù)順序?yàn)椋狠斎雲(yún)?shù)在前,輸出參數(shù)在后。

C/C++函數(shù)參數(shù)分為輸入?yún)?shù)和輸出參數(shù)兩種,有時(shí)輸入?yún)?shù)也會(huì)輸出(譯者注:值被修改時(shí))。輸入?yún)?shù)一般傳值或常數(shù)引用(constreferences),輸出參數(shù)或輸入/輸出參數(shù)為非常數(shù)指針(non-const pointers)。對(duì)參數(shù)排序時(shí),將所有輸入?yún)?shù)置于輸出參數(shù)之前。不要僅僅因?yàn)槭切绿砑拥膮?shù),就將其置于最后,而應(yīng)該依然置于輸出參數(shù)之前。

這一點(diǎn)并不是必須遵循的規(guī)則,輸入/輸出兩用參數(shù)(通常是類/結(jié)構(gòu)體變量)混在其中,會(huì)使得規(guī)則難以遵循。

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

將包含次序標(biāo)準(zhǔn)化可增強(qiáng)可讀性、避免隱藏依賴(hiddendependencies,譯者注:隱藏依賴主要是指包含的文件中編譯時(shí)),次序如下:C庫、C++庫、其他庫的.h、項(xiàng)目?jī)?nèi)的.h。

項(xiàng)目?jī)?nèi)頭文件應(yīng)按照項(xiàng)目源代碼目錄樹結(jié)構(gòu)排列,并且避免使用UNIX文件路徑.(當(dāng)前目錄)和..(父目錄)。例如,公司-awesome-project/src/base/logging.h應(yīng)像這樣被包含:

#include"base/logging.h"

dir/foo.cc的主要作用是執(zhí)行或測(cè)試dir2/foo2.h的功能,foo.cc中包含頭文件的次序如下:

    dir2/foo2.h(優(yōu)先位置,詳情如下)

    C系統(tǒng)文件

    C++系統(tǒng)文件

    其他庫頭文件

    本項(xiàng)目?jī)?nèi)頭文件

這種排序方式可有效減少隱藏依賴,我們希望每一個(gè)頭文件獨(dú)立編譯。最簡(jiǎn)單的實(shí)現(xiàn)方式是將其作為第一個(gè).h文件包含在對(duì)應(yīng)的.cc中。

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

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

舉例來說,公司-awesome-project/src/foo/internal/fooserver.cc的包含次序如下:

#include "foo/public/fooserver.h"  // 優(yōu)先位置

#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"

______________________________________

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

1. 避免多重包含是學(xué)編程時(shí)最基本的要求;

2. 前置聲明是為了降低編譯依賴,防止修改一個(gè)頭文件引發(fā)多米諾效應(yīng);

3. 內(nèi)聯(lián)函數(shù)的合理使用可提高代碼執(zhí)行效率;

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

5. 標(biāo)準(zhǔn)化函數(shù)參數(shù)順序可以提高可讀性和易維護(hù)性(對(duì)函數(shù)參數(shù)的堆?臻g有輕微影響,我以前大多是相同類型放在一起);

6. 包含文件的名稱使用.和..雖然方便卻易混亂,使用比較完整的項(xiàng)目路徑看上去很清晰、很條理,包含文件的次序除了美觀之外,最重要的是可以減少隱藏依賴,使每個(gè)頭文件在“最需要編譯”(對(duì)應(yīng)源文件處:D)的地方編譯,有人提出庫文件放在最后,這樣出錯(cuò)先是項(xiàng)目?jī)?nèi)的文件,頭文件都放在對(duì)應(yīng)源文件的最前面,這一點(diǎn)足以保證內(nèi)部錯(cuò)誤的及時(shí)發(fā)現(xiàn)了。

公司 C++編程風(fēng)格指南(二)作用域1. 命名空間(Namespaces)

在.cc文件中,提倡使用不具名的命名空間(unnamednamespaces,譯者注:不具名的命名空間就像不具名的類一樣,似乎被介紹的很少:-()。使用具名命名空間時(shí),其名稱可基于項(xiàng)目或路徑名稱,不要使用using指示符。

定義:命名空間將全局作用域細(xì)分為不同的、具名的作用域,可有效防止全局作用域的命名沖突。

優(yōu)點(diǎn):命名空間提供了(可嵌套)命名軸線(name axis,譯者注:將命名分割在不同命名空間內(nèi)),當(dāng)然,類也提供了(可嵌套)的命名軸線(譯者注:將命名分割在不同類的作用域內(nèi))。

舉例來說,兩個(gè)不同項(xiàng)目的全局作用域都有一個(gè)類Foo,這樣在編譯或運(yùn)行時(shí)造成沖突。如果每個(gè)項(xiàng)目將代碼置于不同命名空間中,project1::Foo和project2::Foo作為不同符號(hào)自然不會(huì)沖突。

缺點(diǎn):命名空間具有迷惑性,因?yàn)樗鼈兒皖愐粯犹峁┝祟~外的(可嵌套的)命名軸線。在頭文件中使用不具名的空間容易違背C++的唯一定義原則(One Definition Rule (ODR))。

結(jié)論:根據(jù)下文將要提到的策略合理使用命名空間。

1) 不具名命名空間(Unnamed Namespaces)

在.cc文件中,允許甚至提倡使用不具名命名空間,以避免運(yùn)行時(shí)的命名沖突:

namespace{                                  // .cc 文件中

// 命名空間的內(nèi)容無需縮進(jìn)

enum { UNUSED, EOF, ERROR};          // 經(jīng)常使用的符號(hào)

bool AtEof() { return pos_ == EOF; }   // 使用本命名空間內(nèi)的符號(hào)EOF

}  // namespace

然而,與特定類關(guān)聯(lián)的文件作用域聲明在該類中被聲明為類型、靜態(tài)數(shù)據(jù)成員或靜態(tài)成員函數(shù),而不是不具名命名空間的成員。像上文展示的那樣,不具名命名空間結(jié)束時(shí)用注釋// namespace標(biāo)識(shí)。

不能在.h文件中使用不具名命名空間。

2) 具名命名空間(Named Namespaces)

具名命名空間使用方式如下:

命名空間將除文件包含、全局標(biāo)識(shí)的聲明/定義以及類的前置聲明外的整個(gè)源文件封裝起來,以同其他命名空間相區(qū)分。

// .h文件

namespace mynamespace {

// 所有聲明都置于命名空間中

// 注意不要使用縮進(jìn)

class MyClass {

public:

  ...

  void Foo();

};

}  // namespace mynamespace

// .cc文件

namespace mynamespace {

// 函數(shù)定義都置于命名空間中

void MyClass::Foo() {

  ...

}

}  // namespace mynamespace

通常的.cc文件會(huì)包含更多、更復(fù)雜的細(xì)節(jié),包括對(duì)其他命名空間中類的引用等。

#include "a.h"

DEFINE_bool(someflag, false, "dummy flag");

class C;  // 全局命名空間中類C的前置聲明

namespace a { class A; }  // 命名空間a中的類a::A的前置聲明

namespace b {

...code forb...               // b中的代碼

}  // namespace b

不要聲明命名空間std下的任何內(nèi)容,包括標(biāo)準(zhǔn)庫類的前置聲明。聲明std下的實(shí)體會(huì)導(dǎo)致不明確的行為,如,不可移植。聲明標(biāo)準(zhǔn)庫下的實(shí)體,需要包含對(duì)應(yīng)的頭文件。

最好不要使用using指示符,以保證命名空間下的所有名稱都可以正常使用。

// 禁止——污染命名空間

using namespace foo;

在.cc文件、.h文件的函數(shù)、方法或類中,可以使用using。

// 允許:.cc文件中

// .h文件中,必須在函數(shù)、方法或類的內(nèi)部使用

using ::foo::bar;

在.cc文件、.h文件的函數(shù)、方法或類中,還可以使用命名空間別名。

// 允許:.cc文件中

// .h文件中,必須在函數(shù)、方法或類的內(nèi)部使用

namespace fbz = ::foo::bar::baz;

2. 嵌套類(Nested Class)

當(dāng)公開嵌套類作為接口的一部分時(shí),雖然可以直接將他們保持在全局作用域中,但將嵌套類的聲明置于命名空間中是更好的選擇。

定義:可以在一個(gè)類中定義另一個(gè)類,嵌套類也稱成員類(member class)。

class Foo {

private:

  // Bar是嵌套在Foo中的成員類

  class Bar {

    ...

  };

};

優(yōu)點(diǎn):當(dāng)嵌套(成員)類只在被嵌套類(enclosing class)中使用時(shí)很有用,將其置于被嵌套類作用域作為被嵌套類的成員不會(huì)污染其他作用域同名類?稍诒磺短最愔星爸寐暶髑短最,在.cc文件中定義嵌套類,避免在被嵌套類中包含嵌套類的定義,因?yàn)榍短最惖亩x通常只與實(shí)現(xiàn)相關(guān)。

缺點(diǎn):只能在被嵌套類的定義中才能前置聲明嵌套類。因此,任何使用Foo::Bar*指針的頭文件必須包含整個(gè)Foo的聲明。

結(jié)論:不要將嵌套類定義為public,除非它們是接口的一部分,比如,某個(gè)方法使用了這個(gè)類的一系列選項(xiàng)。

3. 非成員函數(shù)(Nonmember)、靜態(tài)成員函數(shù)(Static Member)和全局函數(shù)(GlobalFunctions)

使用命名空間中的非成員函數(shù)或靜態(tài)成員函數(shù),盡量不要使用全局函數(shù)。

優(yōu)點(diǎn):某些情況下,非成員函數(shù)和靜態(tài)成員函數(shù)是非常有用的,將非成員函數(shù)置于命名空間中可避免對(duì)全局作用域的污染。

缺點(diǎn):將非成員函數(shù)和靜態(tài)成員函數(shù)作為新類的成員或許更有意義,當(dāng)它們需要訪問外部資源或具有重要依賴時(shí)更是如此。

結(jié)論:

有時(shí),不把函數(shù)限定在類的實(shí)體中是有益的,甚至需要這么做,要么作為靜態(tài)成員,要么作為非成員函數(shù)。非成員函數(shù)不應(yīng)依賴于外部變量,并盡量置于某個(gè)命名空間中。相比單純?yōu)榱朔庋b若干不共享任何靜態(tài)數(shù)據(jù)的靜態(tài)成員函數(shù)而創(chuàng)建類,不如使用命名空間。

定義于同一編譯單元的函數(shù),被其他編譯單元直接調(diào)用可能會(huì)引入不必要的耦合和連接依賴;靜態(tài)成員函數(shù)對(duì)此尤其敏感?梢钥紤]提取到新類中,或者將函數(shù)置于獨(dú)立庫的命名空間中。

如果你確實(shí)需要定義非成員函數(shù),又只是在.cc文件中使用它,可使用不具名命名空間或static關(guān)聯(lián)(如static int Foo() {...})限定其作用域。

4. 局部變量(Local Variables)

將函數(shù)變量盡可能置于最小作用域內(nèi),在聲明變量時(shí)將其初始化。

C++允許在函數(shù)的任何位置聲明變量。我們提倡在盡可能小的作用域中聲明變量,離第一次使用越近越好。這使得代碼易于閱讀,易于定位變量的聲明位置、變量類型和初始值。特別是,應(yīng)使用初始化代替聲明+賦值的方式。

int i;

i = f();        // 壞——初始化和聲明分離

nt j = g();   // 好——初始化時(shí)聲明

注意:gcc可正確執(zhí)行for (int i = 0; i < 10;++i)(i的作用域僅限for循環(huán)),因此其他for循環(huán)中可重用i。if和while等語句中,作用域聲明(scope declaration)同樣是正確的。

while (const char* p = strchr(str, '/')) str = p + 1;

注意:如果變量是一個(gè)對(duì)象,每次進(jìn)入作用域都要調(diào)用其構(gòu)造函數(shù),每次退出作用域都要調(diào)用其析構(gòu)函數(shù)。

// 低效的實(shí)現(xiàn)

for (int i = 0; i < 1000000; ++i) {

  Foo f;  // 構(gòu)造函數(shù)和析構(gòu)函數(shù)分別調(diào)用1000000次!

  f.DoSomething(i);

}

類似變量放到循環(huán)作用域外面聲明要高效的多:

Foo f;  // 構(gòu)造函數(shù)和析構(gòu)函數(shù)只調(diào)用1次

for (int i = 0; i < 1000000; ++i) {

  f.DoSomething(i);

}

5. 全局變量(Global Variables)

class類型的全局變量是被禁止的,內(nèi)建類型的全局變量是允許的,當(dāng)然多線程代碼中非常數(shù)全局變量也是被禁止的。永遠(yuǎn)不要使用函數(shù)返回值初始化全局變量。

不幸的是,全局變量的構(gòu)造函數(shù)、析構(gòu)函數(shù)以及初始化操作的調(diào)用順序只是被部分規(guī)定,每次生成有可能會(huì)有變化,從而導(dǎo)致難以發(fā)現(xiàn)的bugs。

因此,禁止使用class類型的全局變量(包括STL的string, vector等等),因?yàn)樗鼈兊某跏蓟樞蛴锌赡軐?dǎo)致構(gòu)造出現(xiàn)問題。內(nèi)建類型和由內(nèi)建類型構(gòu)成的沒有構(gòu)造函數(shù)的結(jié)構(gòu)體可以使用,如果你一定要使用class類型的全局變量,請(qǐng)使用單件模式(singleton pattern)。

對(duì)于全局的字符串常量,使用C風(fēng)格的字符串,而不要使用STL的字符串:

const char kFrogSays[] = "ribbet";

雖然允許在全局作用域中使用全局變量,使用時(shí)務(wù)必三思。大多數(shù)全局變量應(yīng)該是類的靜態(tài)數(shù)據(jù)成員,或者當(dāng)其只在.cc文件中使用時(shí),將其定義到不具名命名空間中,或者使用靜態(tài)關(guān)聯(lián)以限制變量的作用域。

記住,靜態(tài)成員變量視作全局變量,所以,也不能是class類型!

______________________________________

譯者:這一篇主要提到的是作用域的一些規(guī)則,總結(jié)一下:

1. .cc中的不具名命名空間可避免命名沖突、限定作用域,避免直接使用using提示符污染命名空間;

2. 嵌套類符合局部使用原則,只是不能在其他頭文件中前置聲明,盡量不要public;

3. 盡量不用全局函數(shù)和全局變量,考慮作用域和命名空間限制,盡量單獨(dú)形成編譯單元;

4. 多線程中的全局變量(含靜態(tài)成員變量)不要使用class類型(含STL容器),避免不明確行為導(dǎo)致的bugs。

作用域的使用,除了考慮名稱污染、可讀性之外,主要是為降低耦合度,提高編譯、執(zhí)行效率。

公司 C++編程風(fēng)格指南(三)

這一篇主要提到的是類,Lippman在《Inside The C++ Object Model》第二章中對(duì)構(gòu)造函數(shù)作了詳盡說明,本文中提到的幾個(gè)單詞基本仿該書中譯本侯捷先生的翻譯:

explicit:明確的

implicit:隱含的

trivial:沒有意義的

non-trivial:有意義的


原文地址:公司 C++ Style Guide

類是C++中基本的代碼單元,自然被廣泛使用。本節(jié)列舉了在寫一個(gè)類時(shí)要做什么、不要做什么。

1. 構(gòu)造函數(shù)(Constructor)的職責(zé)

構(gòu)造函數(shù)中只進(jìn)行那些沒有實(shí)際意義的(trivial,譯者注:簡(jiǎn)單初始化對(duì)于程序執(zhí)行沒有實(shí)際的邏輯意義,因?yàn)槌蓡T變量的“有意義”的值大多不在構(gòu)造函數(shù)中確定)初始化,可能的話,使用Init()方法集中初始化為有意義的(non-trivial)數(shù)據(jù)。

定義:在構(gòu)造函數(shù)中執(zhí)行初始化操作。

優(yōu)點(diǎn):排版方便,無需擔(dān)心類是否初始化。

缺點(diǎn):在構(gòu)造函數(shù)中執(zhí)行操作引起的問題有:

1) 構(gòu)造函數(shù)中不易報(bào)告錯(cuò)誤,不能使用異常。

2) 操作失敗會(huì)造成對(duì)象初始化失敗,引起不確定狀態(tài)。

3) 構(gòu)造函數(shù)內(nèi)調(diào)用虛函數(shù),調(diào)用不會(huì)派發(fā)到子類實(shí)現(xiàn)中,即使當(dāng)前沒有子類化實(shí)現(xiàn),將來仍是隱患。

4) 如果有人創(chuàng)建該類型的全局變量(雖然違背了上節(jié)提到的規(guī)則),構(gòu)造函數(shù)將在main()之前被調(diào)用,有可能破壞構(gòu)造函數(shù)中暗含的假設(shè)條件。例如,gflags尚未初始化。

結(jié)論:如果對(duì)象需要有意義的(non-trivial)初始化,考慮使用另外的Init()方法并(或)增加一個(gè)成員標(biāo)記用于指示對(duì)象是否已經(jīng)初始化成功。

2. 默認(rèn)構(gòu)造函數(shù)(Default Constructors)

如果一個(gè)類定義了若干成員變量又沒有其他構(gòu)造函數(shù),需要定義一個(gè)默認(rèn)構(gòu)造函數(shù),否則編譯器將自動(dòng)生產(chǎn)默認(rèn)構(gòu)造函數(shù)。

定義:新建一個(gè)沒有參數(shù)的對(duì)象時(shí),默認(rèn)構(gòu)造函數(shù)被調(diào)用,當(dāng)調(diào)用new[](為數(shù)組)時(shí),默認(rèn)構(gòu)造函數(shù)總是被調(diào)用。

優(yōu)點(diǎn):默認(rèn)將結(jié)構(gòu)體初始化為“不可能的”值,使調(diào)試更加容易。

缺點(diǎn):對(duì)代碼編寫者來說,這是多余的工作。

結(jié)論:

如果類中定義了成員變量,沒有提供其他構(gòu)造函數(shù),你需要定義一個(gè)默認(rèn)構(gòu)造函數(shù)(沒有參數(shù))。默認(rèn)構(gòu)造函數(shù)更適合于初始化對(duì)象,使對(duì)象內(nèi)部狀態(tài)(internal state)一致、有效。

提供默認(rèn)構(gòu)造函數(shù)的原因是:如果你沒有提供其他構(gòu)造函數(shù),又沒有定義默認(rèn)構(gòu)造函數(shù),編譯器將為你自動(dòng)生成一個(gè),編譯器生成的構(gòu)造函數(shù)并不會(huì)對(duì)對(duì)象進(jìn)行初始化。

如果你定義的類繼承現(xiàn)有類,而你又沒有增加新的成員變量,則不需要為新類定義默認(rèn)構(gòu)造函數(shù)。

3. 明確的構(gòu)造函數(shù)(Explicit Constructors)

對(duì)單參數(shù)構(gòu)造函數(shù)使用C++關(guān)鍵字explicit。

定義:通常,只有一個(gè)參數(shù)的構(gòu)造函數(shù)可被用于轉(zhuǎn)換(conversion,譯者注:主要指隱式轉(zhuǎn)換,下文可見),例如,定義了Foo::Foo(string name),當(dāng)向需要傳入一個(gè)Foo對(duì)象的函數(shù)傳入一個(gè)字符串時(shí),構(gòu)造函數(shù)Foo::Foo(string name)被調(diào)用并將該字符串轉(zhuǎn)換為一個(gè)Foo臨時(shí)對(duì)象傳給調(diào)用函數(shù)。看上去很方便,但如果你并不希望如此通過轉(zhuǎn)換生成一個(gè)新對(duì)象的話,麻煩也隨之而來。為避免構(gòu)造函數(shù)被調(diào)用造成隱式轉(zhuǎn)換,可以將其聲明為explicit。

優(yōu)點(diǎn):避免不合時(shí)宜的變換。

缺點(diǎn):無。

結(jié)論:

所有單參數(shù)構(gòu)造函數(shù)必須是明確的。在類定義中,將關(guān)鍵字explicit加到單參數(shù)構(gòu)造函數(shù)前:explicit Foo(string name);

例外:在少數(shù)情況下,拷貝構(gòu)造函數(shù)可以不聲明為explicit;特意作為其他類的透明包裝器的類。類似例外情況應(yīng)在注釋中明確說明。

4. 拷貝構(gòu)造函數(shù)(Copy Constructors)

僅在代碼中需要拷貝一個(gè)類對(duì)象的時(shí)候使用拷貝構(gòu)造函數(shù);不需要拷貝時(shí)應(yīng)使用DISALLOW_COPY_AND_ASSIGN。

定義:通過拷貝新建對(duì)象時(shí)可使用拷貝構(gòu)造函數(shù)(特別是對(duì)象的傳值時(shí))。

優(yōu)點(diǎn):拷貝構(gòu)造函數(shù)使得拷貝對(duì)象更加容易,STL容器要求所有內(nèi)容可拷貝、可賦值。

缺點(diǎn):C++中對(duì)象的隱式拷貝是導(dǎo)致很多性能問題和bugs的根源。拷貝構(gòu)造函數(shù)降低了代碼可讀性,相比按引用傳遞,跟蹤按值傳遞的對(duì)象更加困難,對(duì)象修改的地方變得難以捉摸。

結(jié)論:

大量的類并不需要可拷貝,也不需要一個(gè)拷貝構(gòu)造函數(shù)或賦值操作(assignmentoperator)。不幸的是,如果你不主動(dòng)聲明它們,編譯器會(huì)為你自動(dòng)生成,而且是public的。

可以考慮在類的private中添加空的(dummy)拷貝構(gòu)造函數(shù)和賦值操作,只有聲明,沒有定義。由于這些空程序聲明為private,當(dāng)其他代碼試圖使用它們的時(shí)候,編譯器將報(bào)錯(cuò)。為了方便,可以使用宏DISALLOW_COPY_AND_ASSIGN:

// 禁止使用拷貝構(gòu)造函數(shù)和賦值操作的宏

// 應(yīng)在類的private:中使用

#defineDISALLOW_COPY_AND_ASSIGN(TypeName) \

  TypeName(constTypeName&);              \

  void operator=(const TypeName&)

class Foo {

public:

  Foo(int f);

  ~Foo();

private:

  DISALLOW_COPY_AND_ASSIGN(Foo);

};

如上所述,絕大多數(shù)情況下都應(yīng)使用DISALLOW_COPY_AND_ASSIGN,如果類確實(shí)需要可拷貝,應(yīng)在該類的頭文件中說明原由,并適當(dāng)定義拷貝構(gòu)造函數(shù)和賦值操作,注意在operator=中檢測(cè)自賦值(self-assignment)情況。

在將類作為STL容器值得時(shí)候,你可能有使類可拷貝的沖動(dòng)。類似情況下,真正該做的是使用指針指向STL容器中的對(duì)象,可以考慮使用std::tr1::shared_ptr。

5. 結(jié)構(gòu)體和類(Structs vs. Classes)

僅當(dāng)只有數(shù)據(jù)時(shí)使用struct,其它一概使用class。

在C++中,關(guān)鍵字struct和class幾乎含義等同,我們?yōu)槠淙藶樘砑诱Z義,以便為定義的數(shù)據(jù)類型合理選擇使用哪個(gè)關(guān)鍵字。

struct被用在僅包含數(shù)據(jù)的消極對(duì)象(passiveobjects)上,可能包括有關(guān)聯(lián)的常量,但沒有存取數(shù)據(jù)成員之外的函數(shù)功能,而存取功能通過直接訪問實(shí)現(xiàn)而無需方法調(diào)用,這兒提到的方法是指只用于處理數(shù)據(jù)成員的,如構(gòu)造函數(shù)、析構(gòu)函數(shù)、Initialize()、Reset()、Validate()。

如果需要更多的函數(shù)功能,class更適合,如果不確定的話,直接使用class。

如果與STL結(jié)合,對(duì)于仿函數(shù)(functors)和特性(traits)可以不用class而是使用struct。

注意:類和結(jié)構(gòu)體的成員變量使用不同的命名規(guī)則。

6. 繼承(Inheritance)

使用組合(composition,譯者注,這一點(diǎn)也是GoF在《Design Patterns》里反復(fù)強(qiáng)調(diào)的)通常比使用繼承更適宜,如果使用繼承的話,只使用公共繼承。

定義:當(dāng)子類繼承基類時(shí),子類包含了父基類所有數(shù)據(jù)及操作的定義。C++實(shí)踐中,繼承主要用于兩種場(chǎng)合:實(shí)現(xiàn)繼承(implementation inheritance),子類繼承父類的實(shí)現(xiàn)代碼;接口繼承(interface inheritance),子類僅繼承父類的方法名稱。

優(yōu)點(diǎn):實(shí)現(xiàn)繼承通過原封不動(dòng)的重用基類代碼減少了代碼量。由于繼承是編譯時(shí)聲明(compile-time declaration),編碼者和編譯器都可以理解相應(yīng)操作并發(fā)現(xiàn)錯(cuò)誤。接口繼承可用于程序上增強(qiáng)類的特定API的功能,在類沒有定義API的必要實(shí)現(xiàn)時(shí),編譯器同樣可以偵錯(cuò)。

缺點(diǎn):對(duì)于實(shí)現(xiàn)繼承,由于實(shí)現(xiàn)子類的代碼在父類和子類間延展,要理解其實(shí)現(xiàn)變得更加困難。子類不能重寫父類的非虛函數(shù),當(dāng)然也就不能修改其實(shí)現(xiàn)。基類也可能定義了一些數(shù)據(jù)成員,還要區(qū)分基類的物理輪廓(physical layout)。

結(jié)論:

所有繼承必須是public的,如果想私有繼承的話,應(yīng)該采取包含基類實(shí)例作為成員的方式作為替代。

不要過多使用實(shí)現(xiàn)繼承,組合通常更合適一些。努力做到只在“是一個(gè)”("is-a",譯者注,其他"has-a"情況下請(qǐng)使用組合)的情況下使用繼承:如果Bar的確“是一種”Foo,才令Bar是Foo的子類。

必要的話,令析構(gòu)函數(shù)為virtual,必要是指,如果該類具有虛函數(shù),其析構(gòu)函數(shù)應(yīng)該為虛函數(shù)。

譯者注:至于子類沒有額外數(shù)據(jù)成員,甚至父類也沒有任何數(shù)據(jù)成員的特殊情況下,析構(gòu)函數(shù)的調(diào)用是否必要是語義爭(zhēng)論,從編程設(shè)計(jì)規(guī)范的角度看,在含有虛函數(shù)的父類中,定義虛析構(gòu)函數(shù)絕對(duì)必要。

限定僅在子類訪問的成員函數(shù)為protected,需要注意的是數(shù)據(jù)成員應(yīng)始終為私有。

當(dāng)重定義派生的虛函數(shù)時(shí),在派生類中明確聲明其為virtual。根本原因:如果遺漏virtual,閱讀者需要檢索類的所有祖先以確定該函數(shù)是否為虛函數(shù)(譯者注,雖然不影響其為虛函數(shù)的本質(zhì))。

7. 多重繼承(Multiple Inheritance)

真正需要用到多重實(shí)現(xiàn)繼承(multipleimplementation inheritance)的時(shí)候非常少,只有當(dāng)最多一個(gè)基類中含有實(shí)現(xiàn),其他基類都是以Interface為后綴的純接口類時(shí)才會(huì)使用多重繼承。

定義:多重繼承允許子類擁有多個(gè)基類,要將作為純接口的基類和具有實(shí)現(xiàn)的基類區(qū)別開來。

優(yōu)點(diǎn):相比單繼承,多重實(shí)現(xiàn)繼承可令你重用更多代碼。

缺點(diǎn):真正需要用到多重實(shí)現(xiàn)繼承的時(shí)候非常少,多重實(shí)現(xiàn)繼承看上去是不錯(cuò)的解決方案,通?梢哉业礁用鞔_、清晰的、不同的解決方案。

結(jié)論:只有當(dāng)所有超類(superclass)除第一個(gè)外都是純接口時(shí)才能使用多重繼承。為確保它們是純接口,這些類必須以Interface為后綴。

注意:關(guān)于此規(guī)則,Windows下有種例外情況(譯者注,將在本譯文最后一篇的規(guī)則例外中闡述)。

8. 接口(Interface)

接口是指滿足特定條件的類,這些類以Interface為后綴(非必需)。

定義:當(dāng)一個(gè)類滿足以下要求時(shí),稱之為純接口:

1) 只有純虛函數(shù)("=0")和靜態(tài)函數(shù)(下文提到的析構(gòu)函數(shù)除外);

2) 沒有非靜態(tài)數(shù)據(jù)成員;

3) 沒有定義任何構(gòu)造函數(shù)。如果有,也不含參數(shù),并且為protected;

4) 如果是子類,也只能繼承滿足上述條件并以Interface為后綴的類。

接口類不能被直接實(shí)例化,因?yàn)樗暶髁思兲摵瘮?shù)。為確保接口類的所有實(shí)現(xiàn)可被正確銷毀,必須為之聲明虛析構(gòu)函數(shù)(作為第1條規(guī)則的例外,析構(gòu)函數(shù)不能是純虛函數(shù))。具體細(xì)節(jié)可參考Stroustrup的《The C++Programming Language, 3rd edition》第12.4節(jié)。

優(yōu)點(diǎn):以Interface為后綴可令他人知道不能為該接口類增加實(shí)現(xiàn)函數(shù)或非靜態(tài)數(shù)據(jù)成員,這一點(diǎn)對(duì)于多重繼承尤其重要。另外,對(duì)于Java程序員來說,接口的概念已經(jīng)深入人心。

缺點(diǎn):Interface后綴增加了類名長(zhǎng)度,為閱讀和理解帶來不便,同時(shí),接口特性作為實(shí)現(xiàn)細(xì)節(jié)不應(yīng)暴露給客戶。

結(jié)論:。只有在滿足上述需要時(shí),類才以Interface結(jié)尾,但反過來,滿足上述需要的類未必一定以Interface結(jié)尾。

9. 操作符重載(Operator Overloading)

除少數(shù)特定環(huán)境外,不要重載操作符。

定義:一個(gè)類可以定義諸如+、/等操作符,使其可以像內(nèi)建類型一樣直接使用。

優(yōu)點(diǎn):使代碼看上去更加直觀,就像內(nèi)建類型(如int)那樣,重載操作符使那些Equals()、Add()等黯淡無光的函數(shù)名好玩多了。為了使一些模板函數(shù)正確工作,你可能需要定義操作符。

缺點(diǎn):雖然操作符重載令代碼更加直觀,但也有一些不足

1) 混淆直覺,讓你誤以為一些耗時(shí)的操作像內(nèi)建操作那樣輕巧;

2) 查找重載操作符的調(diào)用處更加困難,查找Equals()顯然比同等調(diào)用==容易的多;

3) 有的操作符可以對(duì)指針進(jìn)行操作,容易導(dǎo)致bugs,F(xiàn)oo + 4做的是一件事,而&Foo + 4可能做的是完全不同的另一件事,對(duì)于二者,編譯器都不會(huì)報(bào)錯(cuò),使其很難調(diào)試;

4) 重載還有令你吃驚的副作用,比如,重載操作符&的類不能被前置聲明。

結(jié)論:

一般不要重載操作符,尤其是賦值操作(operator=)比較陰險(xiǎn),應(yīng)避免重載。如果需要的話,可以定義類似Equals()、CopyFrom()等函數(shù)。

然而,極少數(shù)情況下需要重載操作符以便與模板或“標(biāo)準(zhǔn)”C++類銜接(如operator<<(ostream&,const T&)),如果被證明是正當(dāng)?shù)纳锌山邮,但你要盡可能避免這樣做。尤其是不要僅僅為了在STL容器中作為key使用就重載operator==或operator<,取而代之,你應(yīng)該在聲明容器的時(shí)候,創(chuàng)建相等判斷和大小比較的仿函數(shù)類型。

有些STL算法確實(shí)需要重載operator==時(shí)可以這么做,不要忘了提供文檔說明原因。

參考拷貝構(gòu)造函數(shù)函數(shù)重載。

10. 存取控制(Access Control)

將數(shù)據(jù)成員私有化,并提供相關(guān)存取函數(shù),如定義變量foo_及取值函數(shù)foo()、賦值函數(shù)set_foo()。

存取函數(shù)的定義一般內(nèi)聯(lián)在頭文件中。

參考繼承函數(shù)命名。

11. 聲明次序(Declaration Order)

在類中使用特定的聲明次序:public:在private:之前,成員函數(shù)在數(shù)據(jù)成員(變量)前。

定義次序如下:public:、protected:、private:,如果那一塊沒有,直接忽略即可。

每一塊中,聲明次序一般如下:

1) typedefs和enums;

2) 常量;

3) 構(gòu)造函數(shù);

4) 析構(gòu)函數(shù);

5) 成員函數(shù),含靜態(tài)成員函數(shù);

6) 數(shù)據(jù)成員,含靜態(tài)數(shù)據(jù)成員。

宏DISALLOW_COPY_AND_ASSIGN置于private:塊之后,作為類的最后部分。參考拷貝構(gòu)造函數(shù)。

.cc文件中函數(shù)的定義應(yīng)盡可能和聲明次序一致。

不要將大型函數(shù)內(nèi)聯(lián)到類的定義中,通常,只有那些沒有特別意義的或者性能要求高的,并且是比較短小的函數(shù)才被定義為內(nèi)聯(lián)函數(shù)。更多細(xì)節(jié)參考譯文第一篇的內(nèi)聯(lián)函數(shù)。

12. 編寫短小函數(shù)(Write Short Functions)

傾向于選擇短小、凝練的函數(shù)。

長(zhǎng)函數(shù)有時(shí)是恰當(dāng)?shù)模虼藢?duì)于函數(shù)長(zhǎng)度并沒有嚴(yán)格限制。如果函數(shù)超過40行,可以考慮在不影響程序結(jié)構(gòu)的情況下將其分割一下。

即使一個(gè)長(zhǎng)函數(shù)現(xiàn)在工作的非常好,一旦有人對(duì)其修改,有可能出現(xiàn)新的問題,甚至導(dǎo)致難以發(fā)現(xiàn)的bugs。使函數(shù)盡量短小、簡(jiǎn)單,便于他人閱讀和修改代碼。

在處理代碼時(shí),你可能會(huì)發(fā)現(xiàn)復(fù)雜的長(zhǎng)函數(shù),不要害怕修改現(xiàn)有代碼:如果證實(shí)這些代碼使用、調(diào)試?yán)щy,或者你需要使用其中的一小塊,考慮將其分割為更加短小、易于管理的若干函數(shù)。

______________________________________

譯者:關(guān)于類的注意事項(xiàng),總結(jié)一下:

1. 不在構(gòu)造函數(shù)中做太多邏輯相關(guān)的初始化;

2. 編譯器提供的默認(rèn)構(gòu)造函數(shù)不會(huì)對(duì)變量進(jìn)行初始化,如果定義了其他構(gòu)造函數(shù),編譯器不再提供,需要編碼者自行提供默認(rèn)構(gòu)造函數(shù);

3. 為避免隱式轉(zhuǎn)換,需將單參數(shù)構(gòu)造函數(shù)聲明為explicit;

4. 為避免拷貝構(gòu)造函數(shù)、賦值操作的濫用和編譯器自動(dòng)生成,可目前聲明其為private且無需實(shí)現(xiàn);

5. 僅在作為數(shù)據(jù)集合時(shí)使用struct;

6. 組合>實(shí)現(xiàn)繼承>接口繼承>私有繼承,子類重載的虛函數(shù)也要聲明virtual關(guān)鍵字,雖然編譯器允許不這樣做;

7. 避免使用多重繼承,使用時(shí),除一個(gè)基類含有實(shí)現(xiàn)外,其他基類均為純接口;

8. 接口類類名以Interface為后綴,除提供帶實(shí)現(xiàn)的虛析構(gòu)函數(shù)、靜態(tài)成員函數(shù)外,其他均為純虛函數(shù),不定義非靜態(tài)數(shù)據(jù)成員,不提供構(gòu)造函數(shù),提供的話,聲明為protected;

9. 為降低復(fù)雜性,盡量不重載操作符,模板、標(biāo)準(zhǔn)類中使用時(shí)提供文檔說明;

10. 存取函數(shù)一般內(nèi)聯(lián)在頭文件中;

11. 聲明次序:public->protected->private;

12. 函數(shù)體盡量短小、緊湊,功能單一。

公司 C++編程風(fēng)格指南(四)公司特有的風(fēng)情

公司有很多自己實(shí)現(xiàn)的使C++代碼更加健壯的技巧、功能,以及有異于別處的C++的使用方式。

1. 智能指針(Smart Pointers)

如果確實(shí)需要使用智能指針的話,scoped_ptr完全可以勝任。在非常特殊的情況下,例如對(duì)STL容器中對(duì)象,你應(yīng)該只使用std::tr1::shared_ptr,任何情況下都不要使用auto_ptr。

“智能”指針看上去是指針,其實(shí)是附加了語義的對(duì)象。以scoped_ptr為例,scoped_ptr被銷毀時(shí),刪除了它所指向的對(duì)象。shared_ptr也是如此,而且,shared_ptr實(shí)現(xiàn)了引用計(jì)數(shù)(reference-counting),從而只有當(dāng)它所指向的最后一個(gè)對(duì)象被銷毀時(shí),指針才會(huì)被刪除。

一般來說,我們傾向于設(shè)計(jì)對(duì)象隸屬明確的代碼,最明確的對(duì)象隸屬是根本不使用指針,直接將對(duì)象作為一個(gè)域(field)或局部變量使用。另一種極端是引用計(jì)數(shù)指針不屬于任何對(duì)象,這樣設(shè)計(jì)的問題是容易導(dǎo)致循環(huán)引用或其他導(dǎo)致對(duì)象無法刪除的詭異條件,而且在每一次拷貝或賦值時(shí)連原子操作都會(huì)很慢。

雖然不推薦這么做,但有些時(shí)候,引用計(jì)數(shù)指針是最簡(jiǎn)單有效的解決方案。

譯者注:看來,公司所謂的不同之處,在于盡量避免使用智能指針:D,使用時(shí)也盡量局部化,并且,安全第一。

其他C++特性1. 引用參數(shù)(Reference Arguments)

所以按引用傳遞的參數(shù)必須加上const。

定義:在C語言中,如果函數(shù)需要修改變量的值,形參(parameter)必須為指針,如int foo(int *pval)。在C++中,函數(shù)還可以聲明引用形參:int foo(int &val)。

優(yōu)點(diǎn):定義形參為引用避免了像(*pval)++這樣丑陋的代碼,像拷貝構(gòu)造函數(shù)這樣的應(yīng)用也是必需的,而且不像指針那樣不接受空指針NULL。

缺點(diǎn):容易引起誤解,因?yàn)橐迷谡Z法上是值卻擁有指針的語義。

結(jié)論:

函數(shù)形參表中,所有引用必須是const:

void Foo(conststring &in, string *out);

事實(shí)上這是一個(gè)硬性約定:輸入?yún)?shù)為值或常數(shù)引用,輸出參數(shù)為指針;輸入?yún)?shù)可以是常數(shù)指針,但不能使用非常數(shù)引用形參。

在強(qiáng)調(diào)參數(shù)不是拷貝而來,在對(duì)象生命期內(nèi)必須一直存在時(shí)可以使用常數(shù)指針,最好將這些在注釋中詳細(xì)說明。bind2nd和mem_fun等STL適配器不接受引用形參,這種情況下也必須以指針形參聲明函數(shù)。

2. 函數(shù)重載(Function Overloading)

僅在輸入?yún)?shù)類型不同、功能相同時(shí)使用重載函數(shù)(含構(gòu)造函數(shù)),不要使用函數(shù)重載模仿缺省函數(shù)參數(shù)。

定義:可以定義一個(gè)函數(shù)參數(shù)類型為const string&,并定義其重載函數(shù)類型為const char*。

class MyClass {

public:

  void Analyze(const string &text);

  void Analyze(const char *text, size_t textlen);

};

優(yōu)點(diǎn):通過重載不同參數(shù)的同名函數(shù),令代碼更加直觀,模板化代碼需要重載,同時(shí)為訪問者帶來便利。

缺點(diǎn):限制使用重載的一個(gè)原因是在特定調(diào)用處很難確定到底調(diào)用的是哪個(gè)函數(shù),另一個(gè)原因是當(dāng)派生類只重載函數(shù)的部分變量會(huì)令很多人對(duì)繼承語義產(chǎn)生困惑。此外在閱讀庫的客戶端代碼時(shí),因缺省函數(shù)參數(shù)造成不必要的費(fèi)解。

結(jié)論:如果你想重載一個(gè)函數(shù),考慮讓函數(shù)名包含參數(shù)信息,例如,使用AppendString()、AppendInt()而不是Append()。

3. 缺省參數(shù)(Default Arguments)

禁止使用缺省函數(shù)參數(shù)。

優(yōu)點(diǎn):經(jīng)常用到一個(gè)函數(shù)帶有大量缺省值,偶爾會(huì)重寫一下這些值,缺省參數(shù)為很少涉及的例外情況提供了少定義一些函數(shù)的方便。

缺點(diǎn):大家經(jīng)常會(huì)通過查看現(xiàn)有代碼確定如何使用API,缺省參數(shù)使得復(fù)制粘貼以前的代碼難以呈現(xiàn)所有參數(shù),當(dāng)缺省參數(shù)不適用于新代碼時(shí)可能導(dǎo)致重大問題。

結(jié)論:所有參數(shù)必須明確指定,強(qiáng)制程序員考慮API和傳入的各參數(shù)值,避免使用可能不為程序員所知的缺省參數(shù)。

4. 變長(zhǎng)數(shù)組和alloca(Variable-LengthArrays and alloca())

禁止使用變長(zhǎng)數(shù)組和alloca()。

優(yōu)點(diǎn):變長(zhǎng)數(shù)組具有渾然天成的語法,變長(zhǎng)數(shù)組和alloca()也都很高效。

缺點(diǎn):變長(zhǎng)數(shù)組和alloca()不是標(biāo)準(zhǔn)C++的組成部分,更重要的是,它們?cè)诙褩#╯tack)上根據(jù)數(shù)據(jù)分配大小可能導(dǎo)致難以發(fā)現(xiàn)的內(nèi)存泄漏:“在我的機(jī)器上運(yùn)行的好好的,到了產(chǎn)品中卻莫名其妙的掛掉了”。

結(jié)論:

使用安全的分配器(allocator),如scoped_ptr/scoped_array。

5. 友元(Friends)

允許合理使用友元類及友元函數(shù)。

通常將友元定義在同一文件下,避免讀者跑到其他文件中查找其對(duì)某個(gè)類私有成員的使用。經(jīng)常用到友元的一個(gè)地方是將FooBuilder聲明為Foo的友元,F(xiàn)ooBuilder以便可以正確構(gòu)造Foo的內(nèi)部狀態(tài),而無需將該狀態(tài)暴露出來。某些情況下,將一個(gè)單元測(cè)試用類聲明為待測(cè)類的友元會(huì)很方便。

友元延伸了(但沒有打破)類的封裝界線,當(dāng)你希望只允許另一個(gè)類訪問某個(gè)成員時(shí),使用友元通常比將其聲明為public要好得多。當(dāng)然,大多數(shù)類應(yīng)該只提供公共成員與其交互。

6. 異常(Exceptions)

不要使用C++異常。

優(yōu)點(diǎn):

1) 異常允許上層應(yīng)用決定如何處理在底層嵌套函數(shù)中發(fā)生的“不可能發(fā)生”的失敗,不像出錯(cuò)代碼的記錄那么模糊費(fèi)解;

2) 應(yīng)用于其他很多現(xiàn)代語言中,引入異常使得C++與Python、Java及其他與C++相近的語言更加兼容;

3) 許多C++第三方庫使用異常,關(guān)閉異常將導(dǎo)致難以與之結(jié)合;

4) 異常是解決構(gòu)造函數(shù)失敗的唯一方案,雖然可以通過工廠函數(shù)(factoryfunction)或Init()方法模擬異常,但他們分別需要堆分配或新的“非法”狀態(tài);

5) 在測(cè)試框架(testing framework)中,異常確實(shí)很好用。

缺點(diǎn):

1) 在現(xiàn)有函數(shù)中添加throw語句時(shí),必須檢查所有調(diào)用處,即使它們至少具有基本的異常安全保護(hù),或者程序正常結(jié)束,永遠(yuǎn)不可能捕獲該異常。例如:if f() calls g() calls h(),h拋出被f捕獲的異常,g就要當(dāng)心了,避免沒有完全清理;

2) 通俗一點(diǎn)說,異常會(huì)導(dǎo)致程序控制流(control flow)通過查看代碼無法確定:函數(shù)有可能在不確定的地方返回,從而導(dǎo)致代碼管理和調(diào)試?yán)щy,當(dāng)然,你可以通過規(guī)定何時(shí)何地如何使用異常來最小化的降低開銷,卻給開發(fā)人員帶來掌握這些規(guī)定的負(fù)擔(dān);

3) 異常安全需要RAII和不同編碼實(shí)踐。輕松、正確編寫異常安全代碼需要大量支撐。允許使用異常;

4) 加入異常使二進(jìn)制執(zhí)行代碼體積變大,增加了編譯時(shí)長(zhǎng)(或許影響不大),還可能增加地址空間壓力;

5) 異常的實(shí)用性可能會(huì)刺激開發(fā)人員在不恰當(dāng)?shù)臅r(shí)候拋出異常,或者在不安全的地方從異常中恢復(fù),例如,非法用戶輸入可能導(dǎo)致拋出異常。如果允許使用異常會(huì)使得這樣一篇編程風(fēng)格指南長(zhǎng)出很多(譯者注,這個(gè)理由有點(diǎn)牽強(qiáng):-()!

結(jié)論:

從表面上看,使用異常利大于弊,尤其是在新項(xiàng)目中,然而,對(duì)于現(xiàn)有代碼,引入異常會(huì)牽連到所有依賴代碼。如果允許異常在新項(xiàng)目中使用,在跟以前沒有使用異常的代碼整合時(shí)也是一個(gè)麻煩。因?yàn)楣粳F(xiàn)有的大多數(shù)C++代碼都沒有異常處理,引入帶有異常處理的新代碼相當(dāng)困難。

鑒于公司現(xiàn)有代碼不接受異常,在現(xiàn)有代碼中使用異常比在新項(xiàng)目中使用的代價(jià)多少要大一點(diǎn),遷移過程會(huì)比較慢,也容易出錯(cuò)。我們也不相信異常的有效替代方案,如錯(cuò)誤代碼、斷言等,都是嚴(yán)重負(fù)擔(dān)。

我們并不是基于哲學(xué)或道德層面反對(duì)使用異常,而是在實(shí)踐的基礎(chǔ)上。因?yàn)槲覀兿M褂霉旧系拈_源項(xiàng)目,但項(xiàng)目中使用異常會(huì)為此帶來不便,因?yàn)槲覀円步ㄗh不要在公司上的開源項(xiàng)目中使用異常,如果我們需要把這些項(xiàng)目推倒重來顯然不太現(xiàn)實(shí)。

對(duì)于Windows代碼來說,這一點(diǎn)有個(gè)例外(等到最后一篇吧:D)。

譯者注:對(duì)于異常處理,顯然不是短短幾句話能夠說清楚的,以構(gòu)造函數(shù)為例,很多C++書籍上都提到當(dāng)構(gòu)造失敗時(shí)只有異?梢蕴幚,公司禁止使用異常這一點(diǎn),僅僅是為了自身的方便,說大了,無非是基于軟件管理成本上,實(shí)際使用中還是自己決定。

7. 運(yùn)行時(shí)類型識(shí)別(Run-Time Type Information, RTTI)

我們禁止使用RTTI。

定義:RTTI允許程序員在運(yùn)行時(shí)識(shí)別C++類對(duì)象的類型。

優(yōu)點(diǎn):

RTTI在某些單元測(cè)試中非常有用,如在進(jìn)行工廠類測(cè)試時(shí)用于檢驗(yàn)一個(gè)新建對(duì)象是否為期望的動(dòng)態(tài)類型。

除測(cè)試外,極少用到。

缺點(diǎn):運(yùn)行時(shí)識(shí)別類型意味著設(shè)計(jì)本身有問題,如果你需要在運(yùn)行期間確定一個(gè)對(duì)象的類型,這通常說明你需要重新考慮你的類的設(shè)計(jì)。

結(jié)論:

除單元測(cè)試外,不要使用RTTI,如果你發(fā)現(xiàn)需要所寫代碼因?qū)ο箢愋筒煌鴦?dòng)作各異的話,考慮換一種方式識(shí)別對(duì)象類型。

虛函數(shù)可以實(shí)現(xiàn)隨子類類型不同而執(zhí)行不同代碼,工作都是交給對(duì)象本身去完成。

如果工作在對(duì)象之外的代碼中完成,考慮雙重分發(fā)方案,如Visitor模式,可以方便的在對(duì)象本身之外確定類的類型。

如果你認(rèn)為上面的方法你掌握不了,可以使用RTTI,但務(wù)必請(qǐng)三思,不要去手工實(shí)現(xiàn)一個(gè)貌似RTTI的方案(RTTI-like workaround),我們反對(duì)使用RTTI,同樣反對(duì)貼上類型標(biāo)簽的貌似類繼承的替代方案(譯者注,使用就使用吧,不使用也不要造輪子:D)。

8. 類型轉(zhuǎn)換(Casting)

使用static_cast<>()等C++的類型轉(zhuǎn)換,不要使用int y = (int)x或int y = int(x);。

定義:C++引入了有別于C的不同類型的類型轉(zhuǎn)換操作。

優(yōu)點(diǎn):C語言的類型轉(zhuǎn)換問題在于操作比較含糊:有時(shí)是在做強(qiáng)制轉(zhuǎn)換(如(int)3.5),有時(shí)是在做類型轉(zhuǎn)換(如(int)"hello")。另外,C++的類型轉(zhuǎn)換查找更容易、更醒目。

缺點(diǎn):語法比較惡心(nasty)。

結(jié)論:使用C++風(fēng)格而不要使用C風(fēng)格類型轉(zhuǎn)換。

1) static_cast:和C風(fēng)格轉(zhuǎn)換相似可做值的強(qiáng)制轉(zhuǎn)換,或指針的父類到子類的明確的向上轉(zhuǎn)換;

2) const_cast:移除const屬性;

3) reinterpret_cast:指針類型和整型或其他指針間不安全的相互轉(zhuǎn)換,僅在你對(duì)所做一切了然于心時(shí)使用;

4) dynamic_cast:除測(cè)試外不要使用,除單元測(cè)試外,如果你需要在運(yùn)行時(shí)確定類型信息,說明設(shè)計(jì)有缺陷(參考RTTI)。

9. 流(Streams)

只在記錄日志時(shí)使用流。

定義:流是printf()和scanf()的替代。

優(yōu)點(diǎn):有了流,在輸出時(shí)不需要關(guān)心對(duì)象的類型,不用擔(dān)心格式化字符串與參數(shù)列表不匹配(雖然在gcc中使用printf也不存在這個(gè)問題),打開、關(guān)閉對(duì)應(yīng)文件時(shí),流可以自動(dòng)構(gòu)造、析構(gòu)。

缺點(diǎn):流使得pread()等功能函數(shù)很難執(zhí)行,如果不使用printf之類的函數(shù)而是使用流很難對(duì)格式進(jìn)行操作(尤其是常用的格式字符串%.*s),流不支持字符串操作符重新定序(%1s),而這一點(diǎn)對(duì)國際化很有用。

結(jié)論:

不要使用流,除非是日志接口需要,使用printf之類的代替。

使用流還有很多利弊,代碼一致性勝過一切,不要在代碼中使用流。

拓展討論:

對(duì)這一條規(guī)則存在一些爭(zhēng)論,這兒給出深層次原因;貞浳ㄒ恍栽瓌t(Only One Way):我們希望在任何時(shí)候都只使用一種確定的I/O類型,使代碼在所有I/O處保持一致。因此,我們不希望用戶來決定是使用流還是printf + read/write,我們應(yīng)該決定到底用哪一種方式。把日志作為例外是因?yàn)榱鞣浅_m合這么做,也有一定的歷史原因。

流的支持者們主張流是不二之選,但觀點(diǎn)并不是那么清晰有力,他們所指出流的所有優(yōu)勢(shì)也正是其劣勢(shì)所在。流最大的優(yōu)勢(shì)是在輸出時(shí)不需要關(guān)心輸出對(duì)象的類型,這是一個(gè)亮點(diǎn),也是一個(gè)不足:很容易用錯(cuò)類型,而編譯器不會(huì)報(bào)警。使用流時(shí)容易造成的一類錯(cuò)誤是:

cout << this; // Prints the address

cout << *this; // Prints the contents

編譯器不會(huì)報(bào)錯(cuò),因?yàn)?lt;<被重載,就因?yàn)檫@一點(diǎn)我們反對(duì)使用操作符重載。

有人說printf的格式化丑陋不堪、易讀性差,但流也好不到哪兒去?纯聪旅鎯啥未a吧,哪個(gè)更加易讀?

cerr << "Error connecting to '" <<foo->bar()->hostname.first

     <<":" << foo->bar()->hostname.second << ":" << strerror(errno);


fprintf(stderr, "Error connecting to '%s:%u:%s",

       foo->bar()->hostname.first, foo->bar()->hostname.second,

       strerror(errno));

你可能會(huì)說,“把流封裝一下就會(huì)比較好了”,這兒可以,其他地方呢?而且不要忘了,我們的目標(biāo)是使語言盡可能小,而不是添加一些別人需要學(xué)習(xí)的新的內(nèi)容。

每一種方式都是各有利弊,“沒有最好,只有更好”,簡(jiǎn)單化的教條告誡我們必須從中選擇其一,最后的多數(shù)決定是printf + read/write。

10. 前置自增和自減(Preincrement and Predecrement)

對(duì)于迭代器和其他模板對(duì)象使用前綴形式(++i)的自增、自減運(yùn)算符。

定義:對(duì)于變量在自增(++i或i++)或自減(--i或i--)后表達(dá)式的值又沒有沒用到的情況下,需要確定到底是使用前置還是后置的自增自減。

優(yōu)點(diǎn):不考慮返回值的話,前置自增(++i)通常要比后置自增(i++)效率更高,因?yàn)楹笾玫淖栽鲎詼p需要對(duì)表達(dá)式的值i進(jìn)行一次拷貝,如果i是迭代器或其他非數(shù)值類型,拷貝的代價(jià)是比較大的。既然兩種自增方式動(dòng)作一樣(譯者注,不考慮表達(dá)式的值,相信你知道我在說什么),為什么不直接使用前置自增呢?

缺點(diǎn):C語言中,當(dāng)表達(dá)式的值沒有使用時(shí),傳統(tǒng)的做法是使用后置自增,特別是在for循環(huán)中,有些人覺得后置自增更加易懂,因?yàn)檫@很像自然語言,主語(i)在謂語動(dòng)詞(++)前。

結(jié)論:對(duì)簡(jiǎn)單數(shù)值(非對(duì)象)來說,兩種都無所謂,對(duì)迭代器和模板類型來說,要使用前置自增(自減)。

11. const的使用(Use of const)

我們強(qiáng)烈建議你在任何可以使用的情況下都要使用const。

定義:在聲明的變量或參數(shù)前加上關(guān)鍵字const用于指明變量值不可修改(如const int foo),為類中的函數(shù)加上const限定表明該函數(shù)不會(huì)修改類成員變量的狀態(tài)(如class Foo { int Bar(char c) const; };)。

優(yōu)點(diǎn):人們更容易理解變量是如何使用的,編輯器可以更好地進(jìn)行類型檢測(cè)、更好地生成代碼。人們對(duì)編寫正確的代碼更加自信,因?yàn)樗麄冎浪{(diào)用的函數(shù)被限定了能或不能修改變量值。即使是在無鎖的多線程編程中,人們也知道什么樣的函數(shù)是安全的。

缺點(diǎn):如果你向一個(gè)函數(shù)傳入const變量,函數(shù)原型中也必須是const的(否則變量需要const_cast類型轉(zhuǎn)換),在調(diào)用庫函數(shù)時(shí)這尤其是個(gè)麻煩。

結(jié)論:const變量、數(shù)據(jù)成員、函數(shù)和參數(shù)為編譯時(shí)類型檢測(cè)增加了一層保障,更好的盡早發(fā)現(xiàn)錯(cuò)誤。因此,我們強(qiáng)烈建議在任何可以使用的情況下使用const:

1) 如果函數(shù)不會(huì)修改傳入的引用或指針類型的參數(shù),這樣的參數(shù)應(yīng)該為const;

2) 盡可能將函數(shù)聲明為const,訪問函數(shù)應(yīng)該總是const,其他函數(shù)如果不會(huì)修改任何數(shù)據(jù)成員也應(yīng)該是const,不要調(diào)用非const函數(shù),不要返回對(duì)數(shù)據(jù)成員的非const指針或引用;

3) 如果數(shù)據(jù)成員在對(duì)象構(gòu)造之后不再改變,可將其定義為const。

然而,也不要對(duì)const過度使用,像const int * const * const x;就有些過了,即便這樣寫精確描述了x,其實(shí)寫成const int** x就可以了。

關(guān)鍵字mutable可以使用,但是在多線程中是不安全的,使用時(shí)首先要考慮線程安全。

const位置

有人喜歡int const *foo形式不喜歡const int* foo,他們認(rèn)為前者更加一致因此可讀性更好:遵循了const總位于其描述的對(duì)象(int)之后的原則。但是,一致性原則不適用于此,“不要過度使用”的權(quán)威抵消了一致性使用。將const放在前面才更易讀,因?yàn)樵谧匀徽Z言中形容詞(const)是在名詞(int)之前的。

這是說,我們提倡const在前,并不是要求,但要兼顧代碼的一致性!

12. 整型(Integer Types)

C++內(nèi)建整型中,唯一用到的是int,如果程序中需要不同大小的變量,可以使用<stdint.h>中的精確寬度(precise-width)的整型,如int16_t。

定義:C++沒有指定整型的大小,通常人們認(rèn)為short是16位,int是32位,long是32位,long long是64位。

優(yōu)點(diǎn):保持聲明統(tǒng)一。

缺點(diǎn):C++中整型大小因編譯器和體系結(jié)構(gòu)的不同而不同。

結(jié)論

<stdint.h>定義了int16_t、uint32_t、int64_t等整型,在需要確定大小的整型時(shí)可以使用它們代替short、unsigned long long等,在C整型中,只使用int。適當(dāng)情況下,推薦使用標(biāo)準(zhǔn)類型如size_t和ptrdiff_t。

最常使用的是,對(duì)整數(shù)來說,通常不會(huì)用到太大,如循環(huán)計(jì)數(shù)等,可以使用普通的int。你可以認(rèn)為int至少為32位,但不要認(rèn)為它會(huì)多于32位,需要64位整型的話,可以使用int64_t或uint64_t。

對(duì)于大整數(shù),使用int64_t。

不要使用uint32_t等無符號(hào)整型,除非你是在表示一個(gè)位組(bit pattern)而不是一個(gè)數(shù)值。即使數(shù)值不會(huì)為負(fù)值也不要使用無符號(hào)類型,使用斷言(assertion,譯者注,這一點(diǎn)很有道理,計(jì)算機(jī)只會(huì)根據(jù)變量、返回值等有無符號(hào)確定數(shù)值正負(fù),仍然無法確定對(duì)錯(cuò))來保護(hù)數(shù)據(jù)。

無符號(hào)整型

有些人,包括一些教科書作者,推薦使用無符號(hào)類型表示非負(fù)數(shù),類型表明了數(shù)值取值形式。但是,在C語言中,這一優(yōu)點(diǎn)被由其導(dǎo)致的bugs所淹沒?纯矗

for (unsigned int i = foo.Length()-1; i >= 0; --i) ...

上述代碼永遠(yuǎn)不會(huì)終止!有時(shí)gcc會(huì)發(fā)現(xiàn)該bug并報(bào)警,但通常不會(huì)。類似的bug還會(huì)出現(xiàn)在比較有符合變量和無符號(hào)變量時(shí),主要是C的類型提升機(jī)制(type-promotion scheme,C語言中各種內(nèi)建類型之間的提升轉(zhuǎn)換關(guān)系)會(huì)致使無符號(hào)類型的行為出乎你的意料。

因此,使用斷言聲明變量為非負(fù)數(shù),不要使用無符號(hào)型。

13. 64位下的可移植性(64-bit Portability)

代碼在64位和32位的系統(tǒng)中,原則上應(yīng)該都比較友好,尤其對(duì)于輸出、比較、結(jié)構(gòu)對(duì)齊(structure alignment)來說:

1) printf()指定的一些類型在32位和64位系統(tǒng)上可移植性不是很好,C99標(biāo)準(zhǔn)定義了一些可移植的格式。不幸的是,MSVC 7.1并非全部支持,而且標(biāo)準(zhǔn)中也有所遺漏。所以有時(shí)我們就不得不自己定義丑陋的版本(使用標(biāo)準(zhǔn)風(fēng)格要包含文件inttypes.h):

// printf macros for size_t, in the style of inttypes.h

#ifdef _LP64

#define __PRIS_PREFIX "z"

#else

#define __PRIS_PREFIX

#endif


// Use these macros after a % in a printf format string

// to get correct 32/64 bit behavior, like this:

// size_t size = records.size();

// printf("%"PRIuS"\n", size);


#define PRIdS __PRIS_PREFIX "d"

#define PRIxS __PRIS_PREFIX "x"

#define PRIuS __PRIS_PREFIX "u"

#define PRIXS __PRIS_PREFIX "X"

#define PRIoS __PRIS_PREFIX "o"

類型
不要使用
使用
備注
void *(或其他指針類型)
%lx
%p

int64_t
%qd, %lld
%"PRId64"

uint64_t
%qu, %llu, %llx
%"PRIu64", %"PRIx64"

size_t
%u
%"PRIuS", %"PRIxS"
C99指定%zu
ptrdiff_t
%d
%"PRIdS"
C99指定%zd

注意宏P(guān)RI*會(huì)被編譯器擴(kuò)展為獨(dú)立字符串,因此如果使用非常量的格式化字符串,需要將宏的值而不是宏名插入格式中,在使用宏P(guān)RI*時(shí)同樣可以在%后指定長(zhǎng)度等信息。例如,printf("x = %30"PRIuS"\n", x)在32位Linux上將被擴(kuò)展為printf("x = %30" "u" "\n",x),編譯器會(huì)處理為printf("x = %30u\n", x)。

2) 記住sizeof(void *) != sizeof(int),如果需要一個(gè)指針大小的整數(shù)要使用intptr_t。

3) 需要對(duì)結(jié)構(gòu)對(duì)齊加以留心,尤其是對(duì)于存儲(chǔ)在磁盤上的結(jié)構(gòu)體。在64位系統(tǒng)中,任何擁有int64_t/uint64_t成員的類/結(jié)構(gòu)體將默認(rèn)被處理為8字節(jié)對(duì)齊。如果32位和64位代碼共用磁盤上的結(jié)構(gòu)體,需要確保兩種體系結(jié)構(gòu)下的結(jié)構(gòu)體的對(duì)齊一致。大多數(shù)編譯器提供了調(diào)整結(jié)構(gòu)體對(duì)齊的方案。gcc中可使用__attribute__((packed)),MSVC提供了#pragma pack()和__declspec(align())(譯者注,解決方案的項(xiàng)目屬性里也可以直接設(shè)置)。

4) 創(chuàng)建64位常量時(shí)使用LL或ULL作為后綴,如:

int64_t my_value = 0x123456789LL;

uint64_t my_mask = 3ULL << 48;

5) 如果你確實(shí)需要32位和64位系統(tǒng)具有不同代碼,可以在代碼變量前使用。(盡量不要這么做,使用時(shí)盡量使修改局部化)。

14. 預(yù)處理宏(Preprocessor Macros)

使用宏時(shí)要謹(jǐn)慎,盡量以內(nèi)聯(lián)函數(shù)、枚舉和常量代替之。

宏意味著你和編譯器看到的代碼是不同的,因此可能導(dǎo)致異常行為,尤其是當(dāng)宏存在于全局作用域中。

值得慶幸的是,C++中,宏不像C中那么必要。宏內(nèi)聯(lián)效率關(guān)鍵代碼(performance-critical code)可以內(nèi)聯(lián)函數(shù)替代;宏存儲(chǔ)常量可以const變量替代;宏“縮寫”長(zhǎng)變量名可以引用替代;使用宏進(jìn)行條件編譯,這個(gè)……,最好不要這么做,會(huì)令測(cè)試更加痛苦(#define防止頭文件重包含當(dāng)然是個(gè)例外)。

宏可以做一些其他技術(shù)無法實(shí)現(xiàn)的事情,在一些代碼庫(尤其是底層庫中)可以看到宏的某些特性(如字符串化(stringifying,譯者注,使用#)、連接(concatenation,譯者注,使用##)等等)。但在使用前,仔細(xì)考慮一下能不能不使用宏實(shí)現(xiàn)同樣效果。

譯者注:關(guān)于宏的高級(jí)應(yīng)用,可以參考C語言宏的高級(jí)應(yīng)用

下面給出的用法模式可以避免一些使用宏的問題,供使用宏時(shí)參考:

1) 不要在.h文件中定義宏;

2) 使用前正確#define,使用后正確#undef;

3) 不要只是對(duì)已經(jīng)存在的宏使用#undef,選擇一個(gè)不會(huì)沖突的名稱;

4) 不使用會(huì)導(dǎo)致不穩(wěn)定的C++構(gòu)造(unbalanced C++ constructs,譯者注)的宏,至少文檔說明其行為。

15. 0和NULL(0 and NULL)

整數(shù)用0,實(shí)數(shù)用0.0,指針用NULL,字符(串)用'\0'。

整數(shù)用0,實(shí)數(shù)用0.0,這一點(diǎn)是毫無爭(zhēng)議的。

對(duì)于指針(地址值),到底是用0還是NULL,Bjarne Stroustrup建議使用最原始的0,我們建議使用看上去像是指針的NULL,事實(shí)上一些C++編譯器(如gcc 4.1.0)專門提供了NULL的定義,可以給出有用的警告,尤其是sizeof(NULL)和sizeof(0)不相等的情況。

字符(串)用'\0',不僅類型正確而且可讀性好。

16. sizeof(sizeof)

盡可能用sizeof(varname)代替sizeof(type)。

使用sizeof(varname)是因?yàn)楫?dāng)變量類型改變時(shí)代碼自動(dòng)同步,有些情況下sizeof(type)或許有意義,還是要盡量避免,如果變量類型改變的話不能同步。

Struct data;

memset(&data, 0, sizeof(data));

memset(&data, 0, sizeof(Struct));

17. Boost庫(Boost)

只使用Boost中被認(rèn)可的庫。

定義:Boost庫集是一個(gè)非常受歡迎的、同級(jí)評(píng)議的(peer-reviewed)、免費(fèi)的、開源的C++庫。

優(yōu)點(diǎn):Boost代碼質(zhì)量普遍較高、可移植性好,填補(bǔ)了C++標(biāo)準(zhǔn)庫很多空白,如型別特性(type traits)、更完善的綁定(binders)、更好的智能指針,同時(shí)還提供了TR1(標(biāo)準(zhǔn)庫的擴(kuò)展)的實(shí)現(xiàn)。

缺點(diǎn):某些Boost庫提倡的編程實(shí)踐可讀性差,像元程序(metaprogramming)和其他高級(jí)模板技術(shù),以及過度“函數(shù)化”("functional")的編程風(fēng)格。

結(jié)論:為了向閱讀和維護(hù)代碼的人員提供更好的可讀性,我們只允許使用Boost特性的一個(gè)成熟子集,當(dāng)前,這些庫包括:

1) Compressed Pair:boost/compressed_pair.hpp;

2) PointerContainer:boost/ptr_container不包括ptr_array.hpp和序列化(serialization)。

我們會(huì)積極考慮添加可以的Boost特性,所以不必拘泥于該規(guī)則。

______________________________________

譯者:關(guān)于C++特性的注意事項(xiàng),總結(jié)一下:

1. 對(duì)于智能指針,安全第一、方便第二,盡可能局部化(scoped_ptr);

2. 引用形參加上const,否則使用指針形參;

3. 函數(shù)重載的使用要清晰、易讀;

4. 鑒于容易誤用,禁止使用缺省函數(shù)參數(shù)(值得商榷);

5. 禁止使用變長(zhǎng)數(shù)組;

6. 合理使用友元;

7. 為了方便代碼管理,禁止使用異常(值得商榷);

8. 禁止使用RTTI,否則重新設(shè)計(jì)代碼吧;

9. 使用C++風(fēng)格的類型轉(zhuǎn)換,除單元測(cè)試外不要使用dynamic_cast;

10. 使用流還printf+ read/write,it is a problem;

11. 能用前置自增/減不用后置自增/減;

12. const能用則用,提倡const在前;

13. 使用確定大小的整型,除位組外不要使用無符號(hào)型;

14. 格式化輸出及結(jié)構(gòu)對(duì)齊時(shí),注意32位和64位的系統(tǒng)差異;

15. 除字符串化、連接外盡量避免使用宏;

16. 整數(shù)用0,實(shí)數(shù)用0.0,指針用NULL,字符(串)用'\0';

17. 用sizeof(varname)代替sizeof(type);

18. 只使用Boost中被認(rèn)可的庫。

公司 C++編程風(fēng)格指南(五)命名約定

最重要的一致性規(guī)則是命名管理,命名風(fēng)格直接可以直接確定命名實(shí)體是:類型、變量、函數(shù)、常量、宏等等,無需查找實(shí)體聲明,我們大腦中的模式匹配引擎依賴于這些命名規(guī)則。

命名規(guī)則具有一定隨意性,但相比按個(gè)人喜好命名,一致性更重要,所以不管你怎么想,規(guī)則總歸是規(guī)則。

1. 通用命名規(guī)則(General Naming Rules)

函數(shù)命名、變量命名、文件命名應(yīng)具有描述性,不要過度縮寫,類型和變量應(yīng)該是名詞,函數(shù)名可以用“命令性”動(dòng)詞。

如何命名

盡可能給出描述性名稱,不要節(jié)約空間,讓別人很快理解你的代碼更重要,好的命名選擇:

int num_errors;                  // Good.

int num_completed_connections;   // Good.

丑陋的命名使用模糊的縮寫或隨意的字符:

int n;                           // Bad - meaningless.

int nerr;                        // Bad - ambiguousabbreviation.

int n_comp_conns;                // Bad - ambiguousabbreviation.

類型和變量名一般為名詞:如FileOpener、num_errors。

函數(shù)名通常是指令性的,如OpenFile()、set_num_errors(),訪問函數(shù)需要描述的更細(xì)致,要與其訪問的變量相吻合。

縮寫

除非放到項(xiàng)目外也非常明了,否則不要使用縮寫,例如:

// Good

// These show proper names with no abbreviations.

int num_dns_connections; // Most people know what "DNS" stands for.

int price_count_reader;  // OK, price count. Makes sense.


// Bad!

// Abbreviations can be confusing or ambiguous outside asmall group.

int wgc_connections; // Only your group knows what this stands for.

int pc_reader;       // Lots of things can be abbreviated "pc".

不要用省略字母的縮寫:

int error_count; // Good.

int error_cnt;    //Bad.

2. 文件命名(File Names)

文件名要全部小寫,可以包含下劃線(_)或短線(-),按項(xiàng)目約定來。

可接受的文件命名:

my_useful_class.cc

my-useful-class.cc

myusefulclass.cc

C++文件以.cc結(jié)尾,頭文件以.h結(jié)尾。

不要使用已經(jīng)存在于/usr/include下的文件名(譯者注,對(duì)UNIX、Linux等系統(tǒng)而言),如db.h。

通常,盡量讓文件名更加明確,http_server_logs.h就比logs.h要好,定義類時(shí)文件名一般成對(duì)出現(xiàn),如foo_bar.h和foo_bar.cc,對(duì)應(yīng)類FooBar。

內(nèi)聯(lián)函數(shù)必須放在.h文件中,如果內(nèi)聯(lián)函數(shù)比較短,就直接放在.h中。如果代碼比較長(zhǎng),可以放到以-inl.h結(jié)尾的文件中。對(duì)于包含大量?jī)?nèi)聯(lián)代碼的類,可以有三個(gè)文件:

url_table.h      //The class declaration.

url_table.cc     //The class definition.

url_table-inl.h  //Inline functions that include lots of code.

參考第一篇-inl.h文件一節(jié)。

3. 類型命名(Type Names)

類型命名每個(gè)單詞以大寫字母開頭,不包含下劃線:MyExcitingClass、MyExcitingEnum。

所有類型命名——類、結(jié)構(gòu)體、類型定義(typedef)、枚舉——使用相同約定,例如:

// classes and structs

class UrlTable { ...

class UrlTableTester { ...

struct UrlTableProperties { ...


// typedefs

typedef hash_map<UrlTableProperties *, string>PropertiesMap;


// enums

enum UrlTableErrors { ...

4. 變量命名(Variable Names)

變量名一律小寫,單詞間以下劃線相連,類的成員變量以下劃線結(jié)尾,如my_exciting_local_variable、my_exciting_member_variable_。

普通變量命名

舉例:

string table_name; // OK - uses underscore.

string tablename;  // OK - all lowercase.

string tableName;  // Bad - mixed case.

類數(shù)據(jù)成員

結(jié)構(gòu)體的數(shù)據(jù)成員可以和普通變量一樣,不用像類那樣接下劃線:

struct UrlTableProperties {

  string name;

  int num_entries;

}

結(jié)構(gòu)體與類的討論參考第三篇結(jié)構(gòu)體vs.類一節(jié)。

全局變量

對(duì)全局變量沒有特別要求,少用就好,可以以g_或其他易與局部變量區(qū)分的標(biāo)志為前綴。

5. 常量命名(Constant Names)

在名稱前加k:kDaysInAWeek。

所有編譯時(shí)常量(無論是局部的、全局的還是類中的)和其他變量保持些許區(qū)別,k后接大寫字母開頭的單詞:

const int kDaysInAWeek = 7;

6. 函數(shù)命名(Function Names)

普通函數(shù)(regular functions,譯者注,這里與訪問函數(shù)等特殊函數(shù)相對(duì))大小寫混合,存取函數(shù)(accessors andmutators)則要求與變量名匹配:MyExcitingFunction()、MyExcitingMethod()、my_exciting_member_variable()、set_my_exciting_member_variable()。

普通函數(shù)

函數(shù)名以大寫字母開頭,每個(gè)單詞首字母大寫,沒有下劃線:

AddTableEntry()

DeleteUrl()

存取函數(shù)

存取函數(shù)要與存取的變量名匹配,這兒摘錄一個(gè)擁有實(shí)例變量num_entries_的類:

class MyClass {

public:

  ...

  int num_entries()const { return num_entries_; }

  voidset_num_entries(int num_entries) { num_entries_ = num_entries; }


private:

  int num_entries_;

};

其他短小的內(nèi)聯(lián)函數(shù)名也可以使用小寫字母,例如,在循環(huán)中調(diào)用這樣的函數(shù)甚至都不需要緩存其值,小寫命名就可以接受。

譯者注:從這一點(diǎn)上可以看出,小寫的函數(shù)名意味著可以直接內(nèi)聯(lián)使用。

7. 命名空間(Namespace Names)

命名空間的名稱是全小寫的,其命名基于項(xiàng)目名稱和目錄結(jié)構(gòu):公司_awesome_project。

關(guān)于命名空間的討論和如何命名,參考第二篇命名空間。

8. 枚舉命名(Enumerator Names)

枚舉值應(yīng)全部大寫,單詞間以下劃線相連:MY_EXCITING_ENUM_VALUE。

枚舉名稱屬于類型,因此大小寫混合:UrlTableErrors。

enum UrlTableErrors {

  OK = 0,

ERROR_OUT_OF_MEMORY,

ERROR_MALFORMED_INPUT,

};

9. 宏命名(Macro Names)

你并不打算使用宏,對(duì)吧?如果使用,像這樣:MY_MACRO_THAT_SCARES_SMALL_CHILDREN。

參考第四篇預(yù)處理宏,通常是不使用宏的,如果絕對(duì)要用,其命名像枚舉命名一樣全部大寫、使用下劃線:

#define ROUND(x) ...

#define PI_ROUNDED 3.0

MY_EXCITING_ENUM_VALUE

10. 命名規(guī)則例外(Exceptions to Naming Rules)

當(dāng)命名與現(xiàn)有C/C++實(shí)體相似的對(duì)象時(shí),可參考現(xiàn)有命名約定:

bigopen()

函數(shù)名,參考o(jì)pen()

uint

typedef類型定義

bigpos

struct或class,參考pos

sparse_hash_map

STL相似實(shí)體;參考STL命名約定

LONGLONG_MAX

常量,類似INT_MAX

______________________________________

譯者:命名約定就相對(duì)輕松許多,在遵從代碼一致性、可讀性的前提下,略顯隨意:

1. 總體規(guī)則:不要隨意縮寫,如果說ChangeLocalValue寫作ChgLocVal還有情可原的話,把ModifyPlayerName寫作MdfPlyNm就太過分了,除函數(shù)名可適當(dāng)為動(dòng)詞外,其他命名盡量使用清晰易懂的名詞;

2. 宏、枚舉等使用全部大寫+下劃線;

3. 變量(含類、結(jié)構(gòu)體成員變量)、文件、命名空間、存取函數(shù)等使用全部小寫+下劃線,類成員變量以下劃線結(jié)尾,全局變量以g_開頭;

4. 普通函數(shù)、類型(含類與結(jié)構(gòu)體、枚舉類型)、常量等使用大小寫混合,不含下劃線;

5. 參考現(xiàn)有或相近命名約定。

公司 C++編程風(fēng)格指南(六)注釋

注釋雖然寫起來很痛苦,但對(duì)保證代碼可讀性至為重要,下面的規(guī)則描述了應(yīng)該注釋什么、注釋在哪兒。當(dāng)然也要記住,注釋的確很重要,但最好的代碼本身就是文檔(self-documenting),類型和變量命名意義明確要比通過注釋解釋模糊的命名好得多。

注釋是為別人(下一個(gè)需要理解你的代碼的人)而寫的,認(rèn)真點(diǎn)吧,那下一個(gè)人可能就是你!

1. 注釋風(fēng)格(Comment Style)

使用//或/* */,統(tǒng)一就好。

//或/* */都可以,//只是用的更加廣泛,在如何注釋和注釋風(fēng)格上確保統(tǒng)一。

2. 文件注釋(File Comments)

在每一個(gè)文件開頭加入版權(quán)公告,然后是文件內(nèi)容描述。

法律公告和作者信息

每一文件包含以下項(xiàng),依次是:

1) 版權(quán)(copyright statement):如Copyright 2008 公司 Inc.;

2) 許可版本(license boilerplate):為項(xiàng)目選擇合適的許可證版本,如Apache 2.0、BSD、LGPL、GPL;

3) 作者(author line):標(biāo)識(shí)文件的原始作者。

如果你對(duì)其他人創(chuàng)建的文件做了重大修改,將你的信息添加到作者信息里,這樣當(dāng)其他人對(duì)該文件有疑問時(shí)可以知道該聯(lián)系誰。

文件內(nèi)容

每一個(gè)文件版權(quán)許可及作者信息后,都要對(duì)文件內(nèi)容進(jìn)行注釋說明。

通常,.h文件要對(duì)所聲明的類的功能和用法作簡(jiǎn)單說明,.cc文件包含了更多的實(shí)現(xiàn)細(xì)節(jié)或算法討論,如果你感覺這些實(shí)現(xiàn)細(xì)節(jié)或算法討論對(duì)于閱讀有幫助,可以把.cc中的注釋放到.h中,并在.cc中指出文檔在.h中。

不要單純?cè)?h和.cc間復(fù)制注釋,復(fù)制的注釋偏離了實(shí)際意義。

3. 類注釋(Class Comments)

每個(gè)類的定義要附著描述類的功能和用法的注釋。

// Iterates over the contents of a GargantuanTable.  Sample usage:

//   GargantuanTable_Iterator* iter = table->NewIterator();

//    for(iter->Seek("foo"); !iter->done(); iter->Next()) {

//     process(iter->key(), iter->value());

//    }

//    delete iter;

class GargantuanTable_Iterator {

  ...

};


如果你覺得已經(jīng)在文件頂部詳細(xì)描述了該類,想直接簡(jiǎn)單的來上一句“完整描述見文件頂部”的話,還是多少在類中加點(diǎn)注釋吧。

如果類有任何同步前提(synchronizationassumptions),文檔說明之。如果該類的實(shí)例可被多線程訪問,使用時(shí)務(wù)必注意文檔說明。

4. 函數(shù)注釋(Function Comments)

函數(shù)聲明處注釋描述函數(shù)功能,定義處描述函數(shù)實(shí)現(xiàn)。

函數(shù)聲明

注釋于聲明之前,描述函數(shù)功能及用法,注釋使用描述式("Opens the file")而非指令式("Open the file");注釋只是為了描述函數(shù)而不是告訴函數(shù)做什么。通常,注釋不會(huì)描述函數(shù)如何實(shí)現(xiàn),那是定義部分的事情。

函數(shù)聲明處注釋的內(nèi)容:

1) inputs(輸入)及outputs(輸出);

2) 對(duì)類成員函數(shù)而言:函數(shù)調(diào)用期間對(duì)象是否需要保持引用參數(shù),是否會(huì)釋放這些參數(shù);

3) 如果函數(shù)分配了空間,需要由調(diào)用者釋放;

4) 參數(shù)是否可以為NULL;

5) 是否存在函數(shù)使用的性能隱憂(performance implications);

6) 如果函數(shù)是可重入的(re-entrant),其同步前提(synchronization assumptions)是什么?

舉例如下:

// Returns an iterator for this table.  It is the client's

// responsibility to delete the iterator when it is donewith it,

// and it must not use the iterator once theGargantuanTable object

// on which the iterator was created has been deleted.

//

// The iterator is initially positioned at the beginningof the table.

//

// This method is equivalent to:

//    Iterator*iter = table->NewIterator();

//   iter->Seek("");

//    return iter;

// If you are going to immediately seek to another placein the

// returned iterator, it will be faster to useNewIterator()

// and avoid the extra seek.

Iterator* GetIterator() const;


但不要有無謂冗余或顯而易見的注釋,下面的注釋就沒有必要加上“returns false otherwise”,因?yàn)橐呀?jīng)暗含其中了:

// Returns true if the table cannot hold any moreentries.

bool IsTableFull();


注釋構(gòu)造/析構(gòu)函數(shù)時(shí),記住,讀代碼的人知道構(gòu)造/析構(gòu)函數(shù)是什么,所以“destroysthis object”這樣的注釋是沒有意義的。說明構(gòu)造函數(shù)對(duì)參數(shù)做了什么(例如,是否是指針的所有者)以及析構(gòu)函數(shù)清理了什么,如果都是無關(guān)緊要的內(nèi)容,直接省掉注釋,析構(gòu)函數(shù)前沒有注釋是很正常的。

函數(shù)定義

每個(gè)函數(shù)定義時(shí)要以注釋說明函數(shù)功能和實(shí)現(xiàn)要點(diǎn),如使用的漂亮代碼、實(shí)現(xiàn)的簡(jiǎn)要步驟、如此實(shí)現(xiàn)的理由、為什么前半部分要加鎖而后半部分不需要。

不要從.h文件或其他地方的函數(shù)聲明處直接復(fù)制注釋,簡(jiǎn)要說明函數(shù)功能是可以的,但重點(diǎn)要放在如何實(shí)現(xiàn)上。

5. 變量注釋(Variable Comments)

通常變量名本身足以很好說明變量用途,特定情況下,需要額外注釋說明。

類數(shù)據(jù)成員

每個(gè)類數(shù)據(jù)成員(也叫實(shí)例變量或成員變量)應(yīng)注釋說明用途,如果變量可以接受NULL或-1等警戒值(sentinel values),須說明之,如:

private:

// Keeps track ofthe total number of entries in the table.

// Used to ensurewe do not go over the limit. -1 means

// that we don'tyet know how many entries the table has.

intnum_total_entries_;


全局變量(常量)

和數(shù)據(jù)成員相似,所有全局變量(常量)也應(yīng)注釋說明含義及用途,如:

// The total number of tests cases that we run through inthis regression test.

const int kNumTestCases = 6;


6. 實(shí)現(xiàn)注釋(Implementation Comments)

對(duì)于實(shí)現(xiàn)代碼中巧妙的、晦澀的、有趣的、重要的地方加以注釋。

代碼前注釋

出彩的或復(fù)雜的代碼塊前要加注釋,如:

// Divide result by two, taking into account that x

// contains the carry from the add.

for (int i = 0; i < result->size(); i++) {

  x = (x <<8) + (*result);

  (*result)[ i] = x>> 1;

  x &= 1;

}


行注釋

比較隱晦的地方要在行尾加入注釋,可以在代碼之后空兩格加行尾注釋,如:

// If we have enough memory, mmap the data portion too.

mmap_budget = max<int64>(0, mmap_budget -index_->length());

if (mmap_budget >= data_size_ &&!MmapData(mmap_chunk_bytes, mlock))

  return;  // Error already logged.


注意,有兩塊注釋描述這段代碼,當(dāng)函數(shù)返回時(shí)注釋提及錯(cuò)誤已經(jīng)被記入日志。

前后相鄰幾行都有注釋,可以適當(dāng)調(diào)整使之可讀性更好:

...

DoSomething();                 // Comment here so thecomments line up.

DoSomethingElseThatIsLonger();  // Comment here so there are two spacesbetween

                                // the code andthe comment.

...


NULL、true/false、1、2、3……

向函數(shù)傳入、布爾值或整數(shù)時(shí),要注釋說明含義,或使用常量讓代碼望文知意,比較一下:

bool success = CalculateSomething(interesting_value,

                                  10,

                                  false,

                                  NULL);  // What are these arguments??


和:

bool success = CalculateSomething(interesting_value,

                                  10,     // Default base value.

                                  false,  // Not the first time we're calling this.

                                  NULL);  // No callback.


使用常量或描述性變量:

const int kDefaultBaseValue = 10;

const bool kFirstTimeCalling = false;

Callback *null_callback = NULL;

bool success = CalculateSomething(interesting_value,

                                 kDefaultBaseValue,

                                  kFirstTimeCalling,

                                 null_callback);


不要

注意永遠(yuǎn)不要用自然語言翻譯代碼作為注釋,要假設(shè)讀你代碼的人C++比你強(qiáng):D:

// Now go through the b array and make sure that if ioccurs,

// the next element is i+1.

...        //Geez.  What a useless comment.


7. 標(biāo)點(diǎn)、拼寫和語法(Punctuation, Spelling and Grammar)

留意標(biāo)點(diǎn)、拼寫和語法,寫的好的注釋比差的要易讀的多。

注釋一般是包含適當(dāng)大寫和句點(diǎn)(.)的完整的句子,短一點(diǎn)的注釋(如代碼行尾的注釋)可以隨意點(diǎn),依然要注意風(fēng)格的一致性。完整的句子可讀性更好,也可以說明該注釋是完整的而不是一點(diǎn)不成熟的想法。

雖然被別人指出該用分號(hào)(semicolon)的時(shí)候用了逗號(hào)(comma)有點(diǎn)尷尬。清晰易讀的代碼還是很重要的,適當(dāng)?shù)臉?biāo)點(diǎn)、拼寫和語法對(duì)此會(huì)有所幫助。

8. TODO注釋(TODO Comments)

對(duì)那些臨時(shí)的、短期的解決方案,或已經(jīng)夠好但并不完美的代碼使用TODO注釋。

這樣的注釋要使用全大寫的字符串TODO,后面括號(hào)(parentheses)里加上你的大名、郵件地址等,還可以加上冒號(hào)(colon):目的是可以根據(jù)統(tǒng)一的TODO格式進(jìn)行查找:

// TODO(kl@gmail.com): Use a "*" here forconcatenation operator.

// TODO(Zeke) change this to use relations.


如果加上是為了在“將來某一天做某事”,可以加上一個(gè)特定的時(shí)間("Fix by November2005")或事件("Remove this code whenall clients can handle XML responses.")。

______________________________________

譯者:注釋也是比較人性化的約定了:

1. 關(guān)于注釋風(fēng)格,很多C++的coders更喜歡行注釋,C coders或許對(duì)塊注釋依然情有獨(dú)鐘,或者在文件頭大段大段的注釋時(shí)使用塊注釋;

2. 文件注釋可以炫耀你的成就,也是為了捅了簍子別人可以找你;

3. 注釋要言簡(jiǎn)意賅,不要拖沓冗余,復(fù)雜的東西簡(jiǎn)單化和簡(jiǎn)單的東西復(fù)雜化都是要被鄙視的;

4. 對(duì)于Chinese coders來說,用英文注釋還是用中文注釋,it is a problem,但不管怎樣,注釋是為了讓別人看懂,難道是為了炫耀編程語言之外的你的母語或外語水平嗎;

5. 注釋不要太亂,適當(dāng)?shù)目s進(jìn)才會(huì)讓人樂意看,但也沒有必要規(guī)定注釋從第幾列開始(我自己寫代碼的時(shí)候總喜歡這樣),UNIX/LINUX下還可以約定是使用tab還是space,個(gè)人傾向于space;

6. TODO很不錯(cuò),有時(shí)候,注釋確實(shí)是為了標(biāo)記一些未完成的或完成的不盡如人意的地方,這樣一搜索,就知道還有哪些活要干,日志都省了。

公司 C++編程風(fēng)格指南(七)格式

代碼風(fēng)格和格式確實(shí)比較隨意,但一個(gè)項(xiàng)目中所有人遵循同一風(fēng)格是非常容易的,作為個(gè)人未必同意下述格式規(guī)則的每一處,但整個(gè)項(xiàng)目服從統(tǒng)一的編程風(fēng)格是很重要的,這樣做才能讓所有人在閱讀和理解代碼時(shí)更加容易。

1. 行長(zhǎng)度(Line Length)

每一行代碼字符數(shù)不超過80。

我們也認(rèn)識(shí)到這條規(guī)則是存有爭(zhēng)議的,但如此多的代碼都遵照這一規(guī)則,我們感覺一致性更重要。

優(yōu)點(diǎn):提倡該原則的人認(rèn)為強(qiáng)迫他們調(diào)整編輯器窗口大小很野蠻。很多人同時(shí)并排開幾個(gè)窗口,根本沒有多余空間拓寬某個(gè)窗口,人們將窗口最大尺寸加以限定,一致使用80列寬,為什么要改變呢?

缺點(diǎn):反對(duì)該原則的人則認(rèn)為更寬的代碼行更易閱讀,80列的限制是上個(gè)世紀(jì)60年代的大型機(jī)的古板缺陷;現(xiàn)代設(shè)備具有更寬的顯示屏,很輕松的可以顯示更多代碼。

結(jié)論:80個(gè)字符是最大值。例外:

1) 如果一行注釋包含了超過80字符的命令或URL,出于復(fù)制粘貼的方便可以超過80字符;

2) 包含長(zhǎng)路徑的可以超出80列,盡量避免;

3) 頭文件保護(hù)(防止重復(fù)包含第一篇)可以無視該原則。

2. 非ASCII字符(Non-ASCII Characters)

盡量不使用非ASCII字符,使用時(shí)必須使用UTF-8格式。

哪怕是英文,也不應(yīng)將用戶界面的文本硬編碼到源代碼中,因此非ASCII字符要少用。特殊情況下可以適當(dāng)包含此類字符,如,代碼分析外部數(shù)據(jù)文件時(shí),可以適當(dāng)硬編碼數(shù)據(jù)文件中作為分隔符的非ASCII字符串;更常用的是(不需要本地化的)單元測(cè)試代碼可能包含非ASCII字符串。此類情況下,應(yīng)使用UTF-8格式,因?yàn)楹芏喙ぞ叨伎梢岳斫夂吞幚砥渚幋a,十六進(jìn)制編碼也可以,尤其是在增強(qiáng)可讀性的情況下——如"\xEF\xBB\xBF"是Unicode的zero-width no-break space字符,以UTF-8格式包含在源文件中是不可見的。

3. 空格還是制表位(Spaces vs. Tabs)

只使用空格,每次縮進(jìn)2個(gè)空格。

使用空格進(jìn)行縮進(jìn),不要在代碼中使用tabs,設(shè)定編輯器將tab轉(zhuǎn)為空格。

譯者注:在前段時(shí)間的關(guān)于Debian開發(fā)學(xué)習(xí)日記一文中,曾給出針對(duì)C/C++編碼使用的vim配置。

4. 函數(shù)聲明與定義(Function Declarations and Definitions)

返回類型和函數(shù)名在同一行,合適的話,參數(shù)也放在同一行。

函數(shù)看上去像這樣:

ReturnType ClassName::FunctionName(Type par_name1, Typepar_name2) {

  DoSomething();

  ...

}


如果同一行文本較多,容不下所有參數(shù):

ReturnType ClassName::ReallyLongFunctionName(Typepar_name1,

                                            Type par_name2,

                                            Type par_name3) {

  DoSomething();

  ...

}


甚至連第一個(gè)參數(shù)都放不下:

ReturnTypeLongClassName::ReallyReallyReallyLongFunctionName(

    Typepar_name1,  // 4 space indent

    Type par_name2,

    Type par_name3){

DoSomething();  // 2 space indent

  ...

}


注意以下幾點(diǎn):

1) 返回值總是和函數(shù)名在同一行;

2) 左圓括號(hào)(open parenthesis)總是和函數(shù)名在同一行;

3) 函數(shù)名和左圓括號(hào)間沒有空格;

4) 圓括號(hào)與參數(shù)間沒有空格;

5) 左大括號(hào)(open curly brace)總在最后一個(gè)參數(shù)同一行的末尾處;

6) 右大括號(hào)(close curly brace)總是單獨(dú)位于函數(shù)最后一行;

7) 右圓括號(hào)(close parenthesis)和左大括號(hào)間總是有一個(gè)空格;

8) 函數(shù)聲明和實(shí)現(xiàn)處的所有形參名稱必須保持一致;

9) 所有形參應(yīng)盡可能對(duì)齊;

10) 缺省縮進(jìn)為2個(gè)空格;

11) 獨(dú)立封裝的參數(shù)保持4個(gè)空格的縮進(jìn)。

如果函數(shù)為const的,關(guān)鍵字const應(yīng)與最后一個(gè)參數(shù)位于同一行。

// Everything in this function signature fits on a singleline

ReturnType FunctionName(Type par) const {

  ...

}


// This function signature requires multiple lines, but

// the const keyword is on the line with the lastparameter.

ReturnType ReallyLongFunctionName(Type par1,

                                  Type par2)const {

  ...

}


如果有些參數(shù)沒有用到,在函數(shù)定義處將參數(shù)名注釋起來:

// Always have named parameters in interfaces.

class Shape {

public:

  virtual voidRotate(double radians) = 0;

}


// Always have named parameters in the declaration.

class Circle : public Shape {

public:

  virtual voidRotate(double radians);

}


// Comment out unused named parameters in definitions.

void Circle::Rotate(double /*radians*/) {}

// Bad - if someone wants to implement later, it's notclear what the

// variable means.

void Circle::Rotate(double) {}


譯者注:關(guān)于UNIX/Linux風(fēng)格為什么要把左大括號(hào)置于行尾(.cc文件的函數(shù)實(shí)現(xiàn)處,左大括號(hào)位于行首),我的理解是代碼看上去比較簡(jiǎn)約,想想行首除了函數(shù)體被一對(duì)大括號(hào)封在一起之外,只有右大括號(hào)的代碼看上去確實(shí)也舒服;Windows風(fēng)格將左大括號(hào)置于行首的優(yōu)點(diǎn)是匹配情況一目了然。

5. 函數(shù)調(diào)用(Function Calls)

盡量放在同一行,否則,將實(shí)參封裝在圓括號(hào)中。

函數(shù)調(diào)用遵循如下形式:

bool retval = DoSomething(argument1, argument2,argument3);


如果同一行放不下,可斷為多行,后面每一行都和第一個(gè)實(shí)參對(duì)齊,左圓括號(hào)后和右圓括號(hào)前不要留空格:

bool retval = DoSomething(averyveryveryverylongargument1,

                         argument2, argument3);


如果函數(shù)參數(shù)比較多,可以出于可讀性的考慮每行只放一個(gè)參數(shù):

bool retval = DoSomething(argument1,

                         argument2,

                         argument3,

                         argument4);


如果函數(shù)名太長(zhǎng),以至于超過行最大長(zhǎng)度,可以將所有參數(shù)獨(dú)立成行:

if (...) {

  ...

  ...

  if (...) {

   DoSomethingThatRequiresALongFunctionName(

        very_long_argument1,  // 4 space indent

        argument2,

        argument3,

        argument4);

  }


6. 條件語句(Conditionals)

更提倡不在圓括號(hào)中添加空格,關(guān)鍵字else另起一行。

對(duì)基本條件語句有兩種可以接受的格式,一種在圓括號(hào)和條件之間有空格,一種沒有。

最常見的是沒有空格的格式,那種都可以,還是一致性為主。如果你是在修改一個(gè)文件,參考當(dāng)前已有格式;如果是寫新的代碼,參考目錄下或項(xiàng)目中其他文件的格式,還在徘徊的話,就不要加空格了。

if (condition) { // no spaces inside parentheses

  ...  // 2 space indent.

} else {  // Theelse goes on the same line as the closing brace.

  ...

}


如果你傾向于在圓括號(hào)內(nèi)部加空格:

if ( condition ) { // spaces inside parentheses - rare

  ...  // 2 space indent.

} else {  // Theelse goes on the same line as the closing brace.

  ...

}


注意所有情況下if和左圓括號(hào)間有個(gè)空格,右圓括號(hào)和左大括號(hào)(如果使用的話)間也要有個(gè)空格:

if(condition)    // Bad - space missing after IF.

if (condition){  // Bad - space missing before {.

if(condition){   // Doubly bad.

if (condition) { // Good - proper space after IF and before {.


有些條件語句寫在同一行以增強(qiáng)可讀性,只有當(dāng)語句簡(jiǎn)單并且沒有使用else子句時(shí)使用:

if (x == kFoo) return new Foo();

if (x == kBar) return new Bar();


如果語句有else分支是不允許的:

// Not allowed - IF statement on one line when there isan ELSE clause

if (x) DoThis();

else DoThat();


通常,單行語句不需要使用大括號(hào),如果你喜歡也無可厚非,也有人要求if必須使用大括號(hào):

if (condition)

DoSomething();  // 2 space indent.


if (condition) {

DoSomething();  // 2 space indent.

}


但如果語句中哪一分支使用了大括號(hào)的話,其他部分也必須使用:

// Not allowed - curly on IF but not ELSE

if (condition) {

  foo;

} else

  bar;


// Not allowed - curly on ELSE but not IF

if (condition)

  foo;

else {

  bar;

}


// Curly braces around both IF and ELSE required because

// one of the clauses used braces.

if (condition) {

  foo;

} else {

  bar;

}


7. 循環(huán)和開關(guān)選擇語句(Loops and Switch Statements)

switch語句可以使用大括號(hào)分塊;空循環(huán)體應(yīng)使用{}或continue。

switch語句中的case塊可以使用大括號(hào)也可以不用,取決于你的喜好,使用時(shí)要依下文所述。

如果有不滿足case枚舉條件的值,要總是包含一個(gè)default(如果有輸入值沒有case去處理,編譯器將報(bào)警)。如果default永不會(huì)執(zhí)行,可以簡(jiǎn)單的使用assert:

switch (var) {

  case 0: {  // 2 space indent

    ...      // 4 space indent

    break;

  }

  case 1: {

    ...

    break;

  }

  default: {

    assert(false);

  }

}


空循環(huán)體應(yīng)使用{}或continue,而不是一個(gè)簡(jiǎn)單的分號(hào):

while (condition) {

  // Repeat testuntil it returns false.

}

for (int i = 0; i < kSomeNumber; ++i) {}  // Good - empty body.

while (condition) continue;  // Good - continue indicates no logic.

while (condition); // Bad - looks like part of do/while loop.


8. 指針和引用表達(dá)式(Pointers and Reference Expressions)

句點(diǎn)(.)或箭頭(->)前后不要有空格,指針/地址操作符(*、&)后不要有空格。

下面是指針和引用表達(dá)式的正確范例:

x = *p;

p = &x;

x = r.y;

x = r->y;


注意:

1) 在訪問成員時(shí),句點(diǎn)或箭頭前后沒有空格;

2) 指針操作符*或&后沒有空格。

在聲明指針變量或參數(shù)時(shí),星號(hào)與類型或變量名緊挨都可以:

// These are fine, space preceding.

char *c;

const string &str;


// These are fine, space following.

char* c;    // butremember to do "char* c, *d, *e, ...;"!

const string& str;

char * c;  // Bad -spaces on both sides of *

const string & str; // Bad - spaces on both sides of &


同一個(gè)文件(新建或現(xiàn)有)中起碼要保持一致。

譯者注:個(gè)人比較習(xí)慣與變量緊挨的方式。

9. 布爾表達(dá)式(Boolean Expressions)

如果一個(gè)布爾表達(dá)式超過標(biāo)準(zhǔn)行寬(80字符),如果斷行要統(tǒng)一一下。

下例中,邏輯與(&&)操作符總位于行尾:

if (this_one_thing > this_other_thing &&

    a_third_thing== a_fourth_thing &&

    yet_another& last_one) {

  ...

}


兩個(gè)邏輯與(&&)操作符都位于行尾,可以考慮額外插入圓括號(hào),合理使用的話對(duì)增強(qiáng)可讀性是很有幫助的。

譯者注:個(gè)人比較習(xí)慣邏輯運(yùn)算符位于行首,邏輯關(guān)系一目了然,各人喜好而已,至于加不加圓括號(hào)的問題,如果你對(duì)優(yōu)先級(jí)了然于胸的話可以不加,但可讀性總是差了些。

10. 函數(shù)返回值(Return Values)

return表達(dá)式中不要使用圓括號(hào)。

函數(shù)返回時(shí)不要使用圓括號(hào):

return x;  // notreturn(x);


11. 變量及數(shù)組初始化(Variable and Array Initialization)

選擇=還是()。

需要做二者之間做出選擇,下面的形式都是正確的:

int x = 3;

int x(3);

string name("Some Name");

string name = "Some Name";


12. 預(yù)處理指令(Preprocessor Directives)

預(yù)處理指令不要縮進(jìn),從行首開始。

即使預(yù)處理指令位于縮進(jìn)代碼塊中,指令也應(yīng)從行首開始。

// Good - directives at beginning of line

  if(lopsided_score) {

#if DISASTER_PENDING     // Correct -- Starts at beginning of line

   DropEverything();

#endif

    BackToNormal();

  }

// Bad - indented directives

  if(lopsided_score) {

    #ifDISASTER_PENDING  // Wrong!  The "#if" should be at beginning ofline

   DropEverything();

    #endif                // Wrong!  Do not indent "#endif"

    BackToNormal();

  }


13. 類格式(Class Format)

聲明屬性依次序是public:、protected:、private:,每次縮進(jìn)1個(gè)空格(譯者注,為什么不是兩個(gè)呢?也有人提倡private在前,對(duì)于聲明了哪些數(shù)據(jù)成員一目了然,還有人提倡依邏輯關(guān)系將變量與操作放在一起,都有道理:-))。

類聲明(對(duì)類注釋不了解的話,參考第六篇中的類注釋一節(jié))的基本格式如下:

class MyClass : public OtherClass {

public:      // Note the 1 space indent!

  MyClass();  // Regular 2 space indent.

  explicitMyClass(int var);

  ~MyClass() {}


  voidSomeFunction();

  void SomeFunctionThatDoesNothing(){

  }


  voidset_some_var(int var) { some_var_ = var; }

  int some_var()const { return some_var_; }


private:

  boolSomeInternalFunction();


  int some_var_;

  intsome_other_var_;

DISALLOW_COPY_AND_ASSIGN(MyClass);

};


注意:

1) 所以基類名應(yīng)在80列限制下盡量與子類名放在同一行;

2) 關(guān)鍵詞public:、protected:、private:要縮進(jìn)1個(gè)空格(譯者注,MSVC多使用tab縮進(jìn),且這三個(gè)關(guān)鍵詞沒有縮進(jìn));

3) 除第一個(gè)關(guān)鍵詞(一般是public)外,其他關(guān)鍵詞前空一行,如果類比較小的話也可以不空;

4) 這些關(guān)鍵詞后不要空行;

5) public放在最前面,然后是protected和private;

6) 關(guān)于聲明次序參考第三篇聲明次序一節(jié)。

14. 初始化列表(Initializer Lists)

構(gòu)造函數(shù)初始化列表放在同一行或按四格縮進(jìn)并排幾行。

兩種可以接受的初始化列表格式:

// When it all fits on one line:

MyClass::MyClass(int var) : some_var_(var),some_other_var_(var + 1) {


// When it requires multiple lines, indent 4 spaces,putting the colon on

// the first initializer line:

MyClass::MyClass(int var)

    :some_var_(var),             // 4 spaceindent

     some_other_var_(var + 1) {  //lined up

  ...

  DoSomething();

  ...

}


15. 命名空間格式化(Namespace Formatting)

命名空間內(nèi)容不縮進(jìn)。

命名空間不添加額外縮進(jìn)層次,例如:

namespace {


void foo() {  //Correct.  No extra indentation withinnamespace.

  ...

}


}  // namespace


不要縮進(jìn):

namespace {


  // Wrong.  Indented when it should not be.

  void foo() {

    ...

  }


}  // namespace


16. 水平留白(Horizontal Whitespace)

水平留白的使用因地制宜。不要在行尾添加無謂的留白。

普通

void f(bool b) { // Open braces should always have a space before them.

  ...

int i = 0;  //Semicolons usually have no space before them.

int x[] = { 0 }; // Spaces inside braces for array initialization are

int x[] = {0};   // optional.  If you use them, putthem on both sides!

// Spaces around the colon in inheritance and initializerlists.

class Foo : public Bar {

public:

  // For inlinefunction implementations, put spaces between the braces

  // and theimplementation itself.

  Foo(int b) :Bar(), baz_(b) {}  // No spaces insideempty braces.

  void Reset() {baz_ = 0; }  // Spaces separating bracesfrom implementation.

  ...


添加冗余的留白會(huì)給其他人編輯時(shí)造成額外負(fù)擔(dān),因此,不要加入多余的空格。如果確定一行代碼已經(jīng)修改完畢,將多余的空格去掉;或者在專門清理空格時(shí)去掉(確信沒有其他人在使用)。

循環(huán)和條件語句

if (b) {         // Space after the keyword in conditions and loops.

} else {         // Spaces around else.

}

while (test) {}  // There is usually no space inside parentheses.

switch (i) {

for (int i = 0; i < 5; ++i) {

switch ( i ) {   // Loops and conditions may have spaces inside

if ( test ) {    // parentheses, but this is rare. Be consistent.

for ( int i = 0; i < 5; ++i ) {

for ( ; i < 5 ; ++i) { // For loops always have a space after the

  ...                   // semicolon, and may have aspace before the

                       // semicolon.

switch (i) {

  case 1:         // No space before colon in a switchcase.

    ...

  case 2:break;  // Use a space after a colon if there'scode after it.


操作符

x = 0;             // Assignment operators always have spaces around

                   // them.

x = -5;            // No spaces separating unary operators and their

++x;               // arguments.

if (x && !y)

  ...

v = w * x + y / z; // Binary operators usually have spaces around them,

v = w*x + y/z;     // but it's okay to remove spaces around factors.

v = w * (x + z);   // Parentheses should have no spaces inside them.


模板和轉(zhuǎn)換

vector<string> x;           // No spaces inside the angle

y = static_cast<char*>(x);  // brackets (< and >), before

                            // <, or between>( in a cast.

vector<char *> x;           // Spaces between type and pointerare

                            // okay, but beconsistent.

set<list<string> > x;       // C++ requires a space in > >.

set< list<string> > x;      // You may optionally make use

                            // symmetricspacing in < <.


17. 垂直留白(Vertical Whitespace)

垂直留白越少越好。

這不僅僅是規(guī)則而是原則問題了:不是非常有必要的話就不要使用空行。尤其是:不要在兩個(gè)函數(shù)定義之間空超過2行,函數(shù)體頭、尾不要有空行,函數(shù)體中也不要隨意添加空行。

基本原則是:同一屏可以顯示越多的代碼,程序的控制流就越容易理解。當(dāng)然,過于密集的代碼塊和過于疏松的代碼塊同樣難看,取決于你的判斷,但通常是越少越好。

函數(shù)頭、尾不要有空行:

void Function() {


  // Unnecessaryblank lines before and after


}


代碼塊頭、尾不要有空行:

while (condition) {

  // Unnecessaryblank line after


}

if (condition) {


  // Unnecessaryblank line before

}


if-else塊之間空一行還可以接受:

if (condition) {

  // Some lines ofcode too small to move to another function,

  // followed by ablank line.


} else {

  // Another blockof code

}


______________________________________

譯者:首先說明,對(duì)于代碼格式,因人、因系統(tǒng)各有優(yōu)缺點(diǎn),但同一個(gè)項(xiàng)目中遵循同一標(biāo)準(zhǔn)還是有必要的:

1. 行寬原則上不超過80列,把22寸的顯示屏都占完,怎么也說不過去;

2. 盡量不使用非ASCII字符,如果使用的話,參考UTF-8格式(尤其是UNIX/Linux下,Windows下可以考慮寬字符),盡量不將字符串常量耦合到代碼中,比如獨(dú)立出資源文件,這不僅僅是風(fēng)格問題了;

3. UNIX/Linux下無條件使用空格,MSVC的話使用Tab也無可厚非;

4. 函數(shù)參數(shù)、邏輯條件、初始化列表:要么所有參數(shù)和函數(shù)名放在同一行,要么所有參數(shù)并排分行;

5. 除函數(shù)定義的左大括號(hào)可以置于行首外,包括函數(shù)/類/結(jié)構(gòu)體/枚舉聲明、各種語句的左大括號(hào)置于行尾,所有右大括號(hào)獨(dú)立成行;

6. ./->操作符前后不留空格,*/&不要前后都留,一個(gè)就可,靠左靠右依各人喜好;

7. 預(yù)處理指令/命名空間不使用額外縮進(jìn),類/結(jié)構(gòu)體/枚舉/函數(shù)/語句使用縮進(jìn);

8. 初始化用=還是()依個(gè)人喜好,統(tǒng)一就好;

9. return不要加();

10. 水平/垂直留白不要濫用,怎么易讀怎么來。

公司 C++編程風(fēng)格指南(八)[完]規(guī)則之例外

前面說明的編碼習(xí)慣基本是強(qiáng)制性的,但所有優(yōu)秀的規(guī)則都允許例外。

1. 現(xiàn)有不統(tǒng)一代碼(Existing Non-conformant Code)

對(duì)于現(xiàn)有不符合既定編程風(fēng)格的代碼可以網(wǎng)開一面。

當(dāng)你修改使用其他風(fēng)格的代碼時(shí),為了與代碼原有風(fēng)格保持一致可以不使用本指南約定。如果不放心可以與代碼原作者或現(xiàn)在的負(fù)責(zé)人員商討,記住,一致性包括原有的一致性。

1. Windows代碼(Windows Code)

Windows程序員有自己的編碼習(xí)慣,主要源于Windows的一些頭文件和其他Microsoft代碼。我們希望任何人都可以順利讀懂你的代碼,所以針對(duì)所有平臺(tái)的C++編碼給出一個(gè)單獨(dú)的指導(dǎo)方案。

如果你一直使用Windows編碼風(fēng)格的,這兒有必要重申一下某些你可能會(huì)忘記的指南(譯者注,我怎么感覺像在被洗腦:D):

1) 不要使用匈牙利命名法(Hungarian notation,如定義整型變量為iNum),使用公司命名約定,包括對(duì)源文件使用.cc擴(kuò)展名;

2) Windows定義了很多原有內(nèi)建類型的同義詞(譯者注,這一點(diǎn),我也很反感),如DWORD、HANDLE等等,在調(diào)用Windows API時(shí)這是完全可以接受甚至鼓勵(lì)的,但還是盡量使用原來的C++類型,例如,使用const TCHAR *而不是LPCTSTR;

3) 使用Microsoft Visual C++進(jìn)行編譯時(shí),將警告級(jí)別設(shè)置為3或更高,并將所有warnings當(dāng)作errors處理;

4) 不要使用#pragma once;作為包含保護(hù),使用C++標(biāo)準(zhǔn)包含保護(hù),包含保護(hù)的文件路徑包含到項(xiàng)目樹頂層(譯者注,#include<prj_name/public/tools.h>);

5) 除非萬不得已,否則不使用任何不標(biāo)準(zhǔn)的擴(kuò)展,如#pragma和__declspec,允許使用__declspec(dllimport)和__declspec(dllexport),但必須通過DLLIMPORT和DLLEXPORT等宏,以便其他人在共享使用這些代碼時(shí)容易放棄這些擴(kuò)展。

在Windows上,只有很少一些偶爾可以不遵守的規(guī)則:

1) 通常我們禁止使用多重繼承,但在使用COM和ATL/WTL類時(shí)可以使用多重繼承,為了執(zhí)行COM或ATL/WTL類及其接口時(shí)可以使用多重實(shí)現(xiàn)繼承;

2) 雖然代碼中不應(yīng)使用異常,但在ATL和部分STL(包括Visual C++的STL)中異常被廣泛使用,使用ATL時(shí),應(yīng)定義_ATL_NO_EXCEPTIONS以屏蔽異常,你要研究一下是否也屏蔽掉STL的異常,如果不屏蔽,開啟編譯器異常也可以,注意這只是為了編譯STL,自己仍然不要寫含異常處理的代碼;

3) 通常每個(gè)項(xiàng)目的每個(gè)源文件中都包含一個(gè)名為StdAfx.h或precompile.h的頭文件方便頭文件預(yù)編譯,為了使代碼方便與其他項(xiàng)目共享,避免顯式包含此文件(precompile.cc除外),使用編譯器選項(xiàng)/FI以自動(dòng)包含;

4) 通常名為resource.h、且只包含宏的資源頭文件,不必拘泥于此風(fēng)格指南。

團(tuán)隊(duì)合作

參考常識(shí),保持一致。

編輯代碼時(shí),花點(diǎn)時(shí)間看看項(xiàng)目中的其他代碼并確定其風(fēng)格,如果其他代碼if語句中使用空格,那么你也要使用。如果其中的注釋用星號(hào)(*)圍成一個(gè)盒子狀,你也這樣做:

/**********************************

* Some comments are here.

* There may be many lines.

**********************************/


編程風(fēng)格指南的使用要點(diǎn)在于提供一個(gè)公共的編碼規(guī)范,所有人可以把精力集中在實(shí)現(xiàn)內(nèi)容而不是表現(xiàn)形式上。我們給出了全局的風(fēng)格規(guī)范,但局部的風(fēng)格也很重要,如果你在一個(gè)文件中新加的代碼和原有代碼風(fēng)格相去甚遠(yuǎn)的話,這就破壞了文件本身的整體美觀也影響閱讀,所以要盡量避免。

好了,關(guān)于編碼風(fēng)格寫的差不多了,代碼本身才是更有趣的,盡情享受吧!


完整的pdf格式文檔51黑下載地址(共58頁):
c編程風(fēng)格(高清版).pdf (763.17 KB, 下載次數(shù): 12)


評(píng)分

參與人數(shù) 1黑幣 +50 收起 理由
admin + 50 共享資料的黑幣獎(jiǎng)勵(lì)!

查看全部評(píng)分

分享到:  QQ好友和群QQ好友和群 QQ空間QQ空間 騰訊微博騰訊微博 騰訊朋友騰訊朋友
收藏收藏 分享淘帖 頂 踩
回復(fù)

使用道具 舉報(bào)

本版積分規(guī)則

手機(jī)版|小黑屋|51黑電子論壇 |51黑電子論壇6群 QQ 管理員QQ:125739409;技術(shù)交流QQ群281945664

Powered by 單片機(jī)教程網(wǎng)

快速回復(fù) 返回頂部 返回列表