Google C++ Style Guide 谷歌 C++編碼風格指南,深入理解華為與谷歌的編程規范——C和C++實踐指南

Google C++ 編程風格指南

Release

Apr 07, 2017

0. ?享

??

4.45

???

Benjy Weinberger, Craig Silverstein, Gregory Eitzmann, Mark Mentovai, Tashana Landray

??

YuleFox, Yang.Y, acgtyrant, lilinsanity

亯??享

? Google Style Guide

? Google 開源項目風格指南 - 中文版

0.1 ????

Google 經常會發布一些開源項目, 意味著會接受來自其他代碼貢獻者的代碼. 但是如果代碼貢獻者的編程風格與

Google 的不一致, 會給代碼閱讀者和其他代碼提交者造成不小的困擾. Google 因此發布了這份自己的編程風格指南,

使所有提交代碼的人都能獲知 Google 的編程風格.

翻譯初衷:

規則的作用就是避免混亂. 但規則本身一定要權威, 有說服力, 并且是理性的. 我們所見過的大部分編程規

范, 其內容或不夠嚴謹, 或闡述過于簡單, 或帶有一定的武斷性.

Google 保持其一貫的嚴謹精神, 5 萬漢字的指南涉及廣泛, 論證嚴密. 我們翻譯該系列指南的主因也正是

其嚴謹性. 嚴謹意味著指南的價值不僅僅局限于它羅列出的規范, 更具參考意義的是它為了列出規范而做

的謹慎權衡過程.

指南不僅列出你要怎么做, 還告訴你為什么要這么做, 哪些情況下可以不這么做, 以及如何權衡其利弊. 其

他團隊未必要完全遵照指南亦步亦趨, 如前面所說, 這份指南是 Google 根據自身實際情況打造的, 適用于

其主導的開源項目. 其他團隊可以參照該指南, 或從中汲取靈感, 建立適合自身實際情況的規范.

我們在翻譯的過程中, 收獲頗多. 希望本系列指南中文版對你同樣能有所幫助.

我們翻譯時也是盡力保持嚴謹, 但水平所限, bug 在所難免. 有任何意見或建議, 可與我們取得聯系.

中文版和英文版一樣, 使用 Artistic License/GPL 開源許可.

中文版修訂歷史:

? 2015-08 : 熱心的清華大學同學 @lilinsanity 完善了「類」章節以及其它一些小章節。至此,對 Google CPP

Style Guide 4.45 的翻譯正式竣工。

? 2015-07 4.45 : acgtyrant 為了學習 C++ 的規范,順便重新翻譯了本 C++ 風格指南,特別是 C++11 的全新

內容。排版大幅度優化,翻譯措辭更地道,添加了新譯者筆記。Google 總部 C++ 工程師 innocentim, 清華大

學不愿意透露姓名的唐馬儒先生,大阪大學大學院情報科學研究科計算機科學專攻博士 farseerfc 和其它 Arch

Linux 中文社區眾幫了譯者不少忙,謝謝他們。因為 C++ Primer 尚未完全入門,暫時沒有翻譯「類」章節和

其它一些小章節。

? 2009-06 3.133 : YuleFox 的 1.0 版已經相當完善, 但原版在近一年的時間里, 其規范也發生了一些變化.

Yang.Y 與 YuleFox 一拍即合, 以項目的形式來延續中文版: Google 開源項目風格指南 - 中文版項目.

主要變化是同步到 3.133 最新英文版本, 做部分勘誤和改善可讀性方面的修改, 并改進排版效果.

Yang.Y 重新翻修, YuleFox 做后續評審.

? 2008-07 1.0 : 出自 YuleFox 的 Blog, 很多地方摘錄的也是該版本.

0.2 ??

C++ 是 Google 大部分開源項目的主要編程語言. 正如每個 C++ 程序員都知道的, C++ 有很多強大的特性, 但

這種強大不可避免的導致它走向復雜,使代碼更容易產生 bug, 難以閱讀和維護.

本指南的目的是通過詳細闡述 C++ 注意事項來駕馭其復雜性. 這些規則在保證代碼易于管理的同時, 也能高效

使用 C++ 的語言特性.

風格, 亦被稱作可讀性, 也就是指導 C++ 編程的約定. 使用術語 “風格” 有些用詞不當, 因為這些習慣遠不止源代

碼文件格式化這么簡單.

使代碼易于管理的方法之一是加強代碼一致性. 讓任何程序員都可以快速讀懂你的代碼這點非常重要. 保持統一

編程風格并遵守約定意味著可以很容易根據 “模式匹配” 規則來推斷各種標識符的含義. 創建通用, 必需的習慣用語

和模式可以使代碼更容易理解. 在一些情況下可能有充分的理由改變某些編程風格, 但我們還是應該遵循一致性原則,

盡量不這么做.

本指南的另一個觀點是 C++ 特性的臃腫. C++ 是一門包含大量高級特性的龐大語言. 某些情況下, 我們會限制

甚至禁止使用某些特性. 這么做是為了保持代碼清爽, 避免這些特性可能導致的各種問題. 指南中列舉了這類特性, 并

解釋為什么這些特性被限制使用.

Google 主導的開源項目均符合本指南的規定.

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

1. ???

通常每一個 .cc 文件都有一個對應的 .h 文件. 也有一些常見例外, 如單元測試代碼和只包含 main() 函數的 .cc

文件.

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

下面的規則將引導你規避使用頭文件時的各種陷阱.

1.1. Self-contained ???

Tip: 頭文件應該能夠自給自足(self-contained, 也就是可以作為第一個頭文件被引入),以 .h 結尾。至于用來插入

文本的文件,說到底它們并不是頭文件,所以應以 .inc 結尾。不允許分離出 -inl.h 頭文件的做法.

所有頭文件要能夠自給自足。換言之,用戶和重構工具不需要為特別場合而包含額外的頭文件。詳言之,一個頭

文件要有1.2. #define 保護,統統包含它所需要的其它頭文件,也不要求定義任何特別 symbols.

不過有一個例外,即一個文件并不是 self-contained 的,而是作為文本插入到代碼某處。或者,文件內容實際上

是其它頭文件的特定平臺(platform-specific)擴展部分。這些文件就要用 .inc 文件擴展名。

如果 .h 文件聲明了一個模板或內聯函數,同時也在該文件加以定義。凡是有用到這些的 .cc 文件,就得統統包

含該頭文件,否則程序可能會在構建中鏈接失敗。不要把這些定義放到分離的 -inl.h 文件里(譯者注:過去該規范

曾提倡把定義放到 -inl.h 里過)。

有個例外:如果某函數模板為所有相關模板參數顯式實例化,或本身就是某類的一個私有成員,那么它就只能定

義在實例化該模板的 .cc 文件里。

1.2. #define??

Tip: 所有頭文件都應該使用 #define 來防止頭文件被多重包含, 命名格式當是: <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_

1.3. ????

Tip: 盡可能地避免使用前置聲明。使用 #include 包含需要的頭文件即可。

定義:

所謂「前置聲明」(forward declaration)是類、函數和模板的純粹聲明,沒伴隨著其定義.

優點:

? 前置聲明能夠節省編譯時間,多余的 #include 會迫使編譯器展開更多的文件,處理更多的輸入。

? 前置聲明能夠節省不必要的重新編譯的時間。#include 使代碼因為頭文件中無關的改動而被重新編譯多次。

缺點:

? 前置聲明隱藏了依賴關系,頭文件改動時,用戶的代碼會跳過必要的重新編譯過程。

? 前置聲明可能會被庫的后續更改所破壞。前置聲明函數或模板有時會妨礙頭文件開發者變動其 API.

例如擴大形參類型,加個自帶默認參數的模板形參等等。

? 前置聲明來自命名空間 std:: 的 symbol 時,其行為未定義。

? 很難判斷什么時候該用前置聲明,什么時候該用 #include 。極端情況下,用前置聲明代替

includes 甚至都會暗暗地改變代碼的含義:

如果 #include 被 B 和 D 的前置聲明替代,test() 就會調用 f(void*) . * 前置聲明了不少來自頭文件的

symbol 時,就會比單單一行的 include 冗長。* 僅僅為了能前置聲明而重構代碼(比如用指針成員代替

對象成員)會使代碼變得更慢更復雜.

結論:

? 盡量避免前置聲明那些定義在其他項目中的實體.

? 函數:總是使用 #include.

? 類模板:優先使用 #include.

至于什么時候包含頭文件,參見 name-and-order-of-includes。

1.4. ????

Tip: 只有當函數只有 10 行甚至更少時才將其定義為內聯函數.

定義:

當函數被聲明為內聯函數之后, 編譯器會將其內聯展開, 而不是按通常的函數調用機制進行調用.

優點:

只要內聯的函數體較小, 內聯該函數可以令目標代碼更加高效. 對于存取函數以及其它函數體比較短, 性能

關鍵的函數, 鼓勵使用內聯.

缺點:

濫用內聯將導致程序變得更慢. 內聯可能使目標代碼量或增或減, 這取決于內聯函數的大小. 內聯非常短

小的存取函數通常會減少代碼大小, 但內聯一個相當大的函數將戲劇性的增加代碼大小. 現代處理器由于

更好的利用了指令緩存, 小巧的代碼往往執行更快。

結論:

一個較為合理的經驗準則是, 不要內聯超過 10 行的函數. 謹慎對待析構函數, 析構函數往往比其表面看起

來要更長, 因為有隱含的成員和基類析構函數被調用!

另一個實用的經驗準則: 內聯那些包含循環或 switch 語句的函數常常是得不償失 (除非在大多數情況下,

這些循環或 switch 語句從不被執行).

有些函數即使聲明為內聯的也不一定會被編譯器內聯, 這點很重要; 比如虛函數和遞歸函數就不會被正常

內聯. 通常, 遞歸函數不應該聲明成內聯函數.(YuleFox 注: 遞歸調用堆棧的展開并不像循環那么簡單, 比

如遞歸層數在編譯時可能是未知的, 大多數編譯器都不支持內聯遞歸函數). 虛函數內聯的主要原因則是想

把它的函數體放在類定義內, 為了圖個方便, 抑或是當作文檔描述其行為, 比如精短的存取函數.

1.5. #include ????亰?

Tip: 使用標準的頭文件包含順序可增強可讀性, 避免隱藏依賴: 相關頭文件, C 庫, C++ 庫, 其他庫的 .h, 本項目內

的 .h.

項目內頭文件應按照項目源代碼目錄樹結構排列, 避免使用 UNIX 特殊的快捷目錄 . (當前目錄) 或 .. (上級目

錄). 例如, google-awesome-project/src/base/logging.h 應該按如下方式包含:

#include "base/logging.h"

又如, dir/foo.cc 的主要作用是實現或測試 dir2/foo2.h 的功能, foo.cc 中包含頭文件的次序如下:

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

2. C 系統文件

3. C++ 系統文件

4. 其他庫的 .h 文件

5. 本項目內 .h 文件

這種優先的順序排序保證當 dir2/foo2.h 遺漏某些必要的庫時,dir/foo.cc 或 dir/foo_test.cc 的構建會立

刻中止。因此這一條規則保證維護這些文件的人們首先看到構建中止的消息而不是維護其他包的人們。

dir/foo.cc 和 dir2/foo2.h 通常位于同一目錄下 (如 base/basictypes_unittest.cc 和 base/basictypes.h),

但也可以放在不同目錄下.

按字母順序對頭文件包含進行二次排序是不錯的主意。注意較老的代碼可不符合這條規則,要在方便的時候改正

它們。

您所依賴的 symbols 被哪些頭文件所定義,您就應該包含(include)哪些頭文件,forward-declaration 情況除

外。比如您要用到 bar.h 中的某個 symbol, 哪怕您所包含的 foo.h 已經包含了 bar.h, 也照樣得包含 bar.h, 除非

foo.h 有明確說明它會自動向您提供 bar.h 中的 symbol. 不過,凡是 cc 文件所對應的「相關頭文件」已經包含的,

就不用再重復包含進其 cc 文件里面了,就像 foo.cc 只包含 foo.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"

例外:

有時,平臺特定(system-specific)代碼需要條件編譯(conditional includes),這些代碼可以放到其它 includes

之后。當然,您的平臺特定代碼也要夠簡練且獨立,比如:

#include "foo/public/fooserver.h"

#include "base/port.h" // For LANG_CXX11.

#ifdef LANG_CXX11

#include <initializer_list>

#endif // LANG_CXX11

??

(YuleFox) ??

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

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

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

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

5. 標準化函數參數順序可以提高可讀性和易維護性 (對函數參數的堆棧空間有輕微影響, 我以前大多是相同類型放

在一起);

6. 包含文件的名稱使用 . 和 .. 雖然方便卻易混亂, 使用比較完整的項目路徑看上去很清晰, 很條理, 包含文件的

次序除了美觀之外, 最重要的是可以減少隱藏依賴, 使每個頭文件在 “最需要編譯” (對應源文件處:D) 的地方編

譯, 有人提出庫文件放在最后, 這樣出錯先是項目內的文件, 頭文件都放在對應源文件的最前面, 這一點足以保證

內部錯誤的及時發現了.

??δacgtyrantε??

1. 原來還真有項目用 #includes 來插入文本,且其文件擴展名 .inc 看上去也很科學。

2. Google 已經不再提倡 -inl.h 用法。

3. 注意,前置聲明的類是不完全類型(incomplete type),我們只能定義指向該類型的指針或引用,或者聲明(但

不能定義)以不完全類型作為參數或者返回類型的函數。畢竟編譯器不知道不完全類型的定義,我們不能創建

其類的任何對象,也不能聲明成類內部的數據成員。

4. 類內部的函數一般會自動內聯。所以某函數一旦不需要內聯,其定義就不要再放在頭文件里,而是放到對應的

.cc 文件里。這樣可以保持頭文件的類相當精煉,也很好地貫徹了聲明與定義分離的原則。

5. 在 #include 中插入空行以分割相關頭文件, C 庫, C++ 庫, 其他庫的 .h 和本項目內的 .h 是個好習慣。

2. ???

2.1. ??グ?

Tip: 鼓勵在 .cc 文件內使用匿名名字空間. 使用具名的名字空間時, 其名稱可基于項目名或相對路徑. 禁止使用

using 指示(using-directive)。禁止使用內聯命名空間(inline namespace)。

定義:

名字空間將全局作用域細分為獨立的, 具名的作用域, 可有效防止全局作用域的命名沖突.

優點:

雖然類已經提供了(可嵌套的)命名軸線 (YuleFox 注: 將命名分割在不同類的作用域內), 名字空間在這

基礎上又封裝了一層.

舉例來說, 兩個不同項目的全局作用域都有一個類 Foo, 這樣在編譯或運行時造成沖突. 如果每個項目將代

碼置于不同名字空間中, project1::Foo 和 project2::Foo 作為不同符號自然不會沖突.

內聯命名空間會自動把內部的標識符放到外層作用域,比如:

namespace X {

inline namespace Y {

void foo();

}

}

X::Y::foo() 與 X::foo() 彼此可代替。內聯命名空間主要用來保持跨版本的 ABI 兼容性。

缺點:

名字空間具有迷惑性, 因為它們和類一樣提供了額外的 (可嵌套的) 命名軸線.

命名空間很容易令人迷惑,畢竟它們不再受其聲明所在命名空間的限制。內聯命名空間只在大型版本控制

里有用。

在頭文件中使用匿名空間導致違背 C++ 的唯一定義原則 (One Definition Rule (ODR)).

結論:

根據下文將要提到的策略合理使用命名空間.

2.1.1. ????グ?

? 在 .cc 文件中, 允許甚至鼓勵使用匿名名字空間, 以避免運行時的命名沖突:

namespace { // .cc 文件中

// 名字空間的內容無需縮進

enum { kUNUSED, kEOF, kERROR }; // 經常使用的符號

bool AtEof() { return pos_ == kEOF; } // 使用本名字空間內的符號 EOF

} // namespace

然而, 與特定類關聯的文件作用域聲明在該類中被聲明為類型, 靜態數據成員或靜態成員函數, 而不是匿名名字空

間的成員. 如上例所示, 匿名空間結束時用注釋 // namespace 標識.

? 不要在 .h 文件中使用匿名名字空間.

2.1.2. ?????グ?

具名的名字空間使用方式如下:

? 用名字空間把文件包含, gflags 的聲明/定義, 以及類的前置聲明以外的整個源文件封裝起來, 以區別于其它名字

空間:

// .h 文件

namespace mynamespace {

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

// 注意不要使用縮進

class MyClass {

public:

void Foo();

};

} // namespace mynamespace

// .cc 文件

namespace mynamespace {

// 函數定義都置于命名空間中

void MyClass::Foo() {

}

} // namespace mynamespace

通常的 .cc 文件包含更多, 更復雜的細節, 比如引用其他名字空間的類等.

#include “a.h”

DEFINE_bool(someflag, false, “dummy flag”);

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

namespace a { class A; } // a::A 的前置聲明

namespace b {

…code for b… // b 中的代碼

} // namespace b

? 不要在名字空間 std 內聲明任何東西, 包括標準庫的類前置聲明. 在 std 名字空間聲明實體會導致不確定的問

題, 比如不可移植. 聲明標準庫下的實體, 需要包含對應的頭文件.

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

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

using namespace foo;

? 在 .cc 文件, .h 文件的函數, 方法或類中, 可以使用 using 聲明。

// 允許: .cc 文件中

// .h 文件的話, 必須在函數, 方法或類的內部使用

using ::foo::bar;

? 在 .cc 文件, .h 文件的函數, 方法或類中, 允許使用名字空間別名.

// 允許: .cc 文件中

// .h 文件的話, 必須在函數, 方法或類的內部使用

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

// 在 .h 文件里

namespace librarian {

//以下別名在所有包含了該頭文件的文件中生效。

namespace pd_s = ::pipeline_diagnostics::sidetable;

inline void my_inline_function() {

// namespace alias local to a function (or method).

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

...

}

} // namespace librarian

注意在.h 文件的別名對包含了該頭文件的所有人可見,所以在公共頭文件(在項目外可用)以及它們

遞歸包含的其它頭文件里,不要用別名。畢竟原則上公共 API 要盡可能地精簡。

? 禁止用內聯命名空間

2.2. ???

Tip: 當公有嵌套類作為接口的一部分時, 雖然可以直接將他們保持在全局作用域中, 但將嵌套類的聲明置于2.1. 名

字空間 內是更好的選擇.

定義: 在一個類內部定義另一個類; 嵌套類也被稱為 成員類 (member class).

class Foo {

private:

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

class Bar {

};

};

優點:

當嵌套 (或成員) 類只被外圍類使用時非常有用; 把它作為外圍類作用域內的成員, 而不是去污染外部作用

域的同名類. 嵌套類可以在外圍類中做前置聲明, 然后在 .cc 文件中定義, 這樣避免在外圍類的聲明中定

義嵌套類, 因為嵌套類的定義通常只與實現相關.

缺點:

嵌套類只能在外圍類的內部做前置聲明. 因此, 任何使用了 Foo::Bar* 指針的頭文件不得不包含類 Foo 的

整個聲明.

結論:

不要將嵌套類定義成公有, 除非它們是接口的一部分, 比如, 嵌套類含有某些方法的一組選項.

2.3. ?????????????????

Tip: 使用靜態成員函數或名字空間內的非成員函數, 盡量不要用裸的全局函數.

優點:

某些情況下, 非成員函數和靜態成員函數是非常有用的, 將非成員函數放在名字空間內可避免污染全局作

用域.

缺點:

將非成員函數和靜態成員函數作為新類的成員或許更有意義, 當它們需要訪問外部資源或具有重要的依賴

關系時更是如此.

結論:

有時, 把函數的定義同類的實例脫鉤是有益的, 甚至是必要的. 這樣的函數可以被定義成靜態成員, 或是非

成員函數. 非成員函數不應依賴于外部變量, 應盡量置于某個名字空間內. 相比單純為了封裝若干不共享

任何靜態數據的靜態成員函數而創建類, 不如使用2.1. 名字空間。

定義在同一編譯單元的函數, 被其他編譯單元直接調用可能會引入不必要的耦合和鏈接時依賴; 靜態成員

函數對此尤其敏感. 可以考慮提取到新類中, 或者將函數置于獨立庫的名字空間內.

如果你必須定義非成員函數, 又只是在 .cc 文件中使用它, 可使用匿名 namespaces‘或 ‘‘static‘ 鏈接關鍵字

(如 static int Foo() {...}) 限定其作用域.

2.4. ????

Tip: 將函數變量盡可能置于最小作用域內, 并在變量聲明時進行初始化.

C++ 允許在函數的任何位置聲明變量. 我們提倡在盡可能小的作用域中聲明變量, 離第一次使用越近越好. 這使

得代碼瀏覽者更容易定位變量聲明的位置, 了解變量的類型和初始值. 特別是,應使用初始化的方式替代聲明再賦值,

比如:

int i;

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

int j = g(); // 好——初始化時聲明

vector<int> v;

v.push_back(1); // 用花括號初始化更好

v.push_back(2);

vector<int> v = {1, 2}; // 好——v 一開始就初始化

注意, GCC 可正確實現了 for (int i = 0; i < 10; ++i) (i 的作用域僅限 for 循環內), 所以其他 for 循環中

可以重新使用 i. 在 if 和 while 等語句中的作用域聲明也是正確的, 如:

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

Warning: 如果變量是一個對象, 每次進入作用域都要調用其構造函數, 每次退出作用域都要調用其

析構函數.

// 低效的實現

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

Foo f; // 構造函數和析構函數分別調用 1000000 次!

f.DoSomething(i);

}

在循環作用域外面聲明這類變量要高效的多:

Foo f; // 構造函數和析構函數只調用 1 次

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

f.DoSomething(i);

}

2.5. ???????

Tip: 禁止使用 class 類型的靜態或全局變量:它們會導致難以發現的 bug 和不確定的構造和析構函數調用順序。

不過 constexpr 變量除外,畢竟它們又不涉及動態初始化或析構。

靜態生存周期的對象,即包括了全局變量,靜態變量,靜態類成員變量和函數靜態變量,都必須是原生數據類型

(POD : Plain Old Data): 即 int, char 和 float, 以及 POD 類型的指針、數組和結構體。

靜態變量的構造函數、析構函數和初始化的順序在 C++ 中是不確定的,甚至隨著構建變化而變化,導致難以發

現的 bug. 所以除了禁用類類型的全局變量,我們也不允許用函數返回值來初始化 POD 變量,除非該函數不涉及

(比如 getenv() 或 getpid())不涉及任何全局變量。(函數作用域里的靜態變量除外,畢竟它的初始化順序是有明確

定義的,而且只會在指令執行到它的聲明那里才會發生。)

同理,全局和靜態變量在程序中斷時會被析構,無論所謂中斷是從 main() 返回還是對 exit() 的調用。析構順

序正好與構造函數調用的順序相反。但既然構造順序未定義,那么析構順序當然也就不定了。比如,在程序結束時某

靜態變量已經被析構了,但代碼還在跑——比如其它線程——并試圖訪問它且失敗;再比如,一個靜態 string 變量也

許會在一個引用了前者的其它變量析構之前被析構掉。

改善以上析構問題的辦法之一是用 quick_exit() 來代替 exit() 并中斷程序。它們的不同之處是前者不會執行

任何析構,也不會執行 atexit() 所綁定的任何 handlers. 如果您想在執行 quick_exit() 來中斷時執行某 handler

(比如刷新 log),您可以把它綁定到 _at_quick_exit(). 如果您想在 exit() 和 quick_exit() 都用上該 handler, 都

綁定上去。

綜上所述,我們只允許 POD 類型的靜態變量,即完全禁用 vector (使用 C 數組替代) 和 string (使用 const

char [])。

如果您確實需要一個 class 類型的靜態或全局變量,可以考慮在 main() 函數或 pthread_once() 內初始化一個

指針且永不回收。注意只能用 raw 指針,別用智能指針,畢竟后者的析構函數涉及到上文指出的不定順序問題。

Note: Yang.Y 譯注:

上文提及的靜態變量泛指靜態生存周期的對象, 包括: 全局變量, 靜態變量, 靜態類成員變量, 以及函數靜態變量.

??

(YuleFox) ??

1. cc 中的匿名名字空間可避免命名沖突, 限定作用域, 避免直接使用 using 關鍵字污染命名空間;

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

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

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

5. 作用域的使用, 除了考慮名稱污染, 可讀性之外, 主要是為降低耦合, 提高編譯/執行效率.

??δ

acgtyrantε??

1. 注意「using 指示(using-directive)」和「using 聲明(using-declaration)」的區別。

2. 匿名名字空間說白了就是文件作用域,就像 C static 聲明的作用域一樣,后者已經被 C++ 標準提倡棄用。

3. 局部變量在聲明的同時進行顯式值初始化,比起隱式初始化再賦值的兩步過程要高效,同時也貫徹了計算機體

系結構重要的概念「局部性(locality)」。

4. 注意別在循環犯大量構造和析構的低級錯誤。

3. ?

類是 C++ 中代碼的基本單元. 顯然, 它們被廣泛使用. 本節列舉了在寫一個類時的主要注意事項.

3.1. ???????

Tip: 不要在構造函數中進行復雜的初始化 (尤其是那些有可能失敗或者需要調用虛函數的初始化).

定義:

在構造函數體中進行初始化操作.

優點:

排版方便, 無需擔心類是否已經初始化.

缺點:

在構造函數中執行操作引起的問題有:

? 構造函數中很難上報錯誤, 不能使用異常.

? 操作失敗會造成對象初始化失敗,進入不確定狀態.

? 如果在構造函數內調用了自身的虛函數, 這類調用是不會重定向到子類的虛函數實現. 即使當前沒有

子類化實現, 將來仍是隱患.

? 如果有人創建該類型的全局變量 (雖然違背了上節提到的規則), 構造函數將先 main() 一步被調用,

有可能破壞構造函數中暗含的假設條件. 例如, gflags 尚未初始化.

結論:

構造函數不得調用虛函數, 或嘗試報告一個非致命錯誤. 如果對象需要進行有意義的 (non-trivial) 初始化,

考慮使用明確的 Init() 方法或使用工廠模式.

3.2. ???

Tip: 如果類中定義了成員變量, 則必須在類中為每個類提供初始化函數或定義一個構造函數. 若未聲明構造函數, 則

編譯器會生成一個默認的構造函數, 這有可能導致某些成員未被初始化或被初始化為不恰當的值.

定義:

new 一個不帶參數的類對象時, 會調用這個類的默認構造函數. 用 new[] 創建數組時, 默認構造函數則總

是被調用. 在類成員里面進行初始化是指聲明一個成員變量的時候使用一個結構例如 int _count = 17

或者 string _name{"abc"} 來替代 int _count 或者 string _name 這樣的形式.

優點:

用戶定義的默認構造函數將在沒有提供初始化操作時將對象初始化. 這樣就保證了對象在被構造之時就處

于一個有效且可用的狀態, 同時保證了對象在被創建時就處于一個顯然” 不可能” 的狀態, 以此幫助調試.

缺點:

對代碼編寫者來說, 這是多余的工作.

如果一個成員變量在聲明時初始化又在構造函數中初始化, 有可能造成混亂, 因為構造函數中的值會覆蓋

掉聲明中的值.

結論:

簡單的初始化用類成員初始化完成, 尤其是當一個成員變量要在多個構造函數里用相同的方式初始化的時

候.

如果你的類中有成員變量沒有在類里面進行初始化, 而且沒有提供其它構造函數, 你必須定義一個 (不帶參

數的) 默認構造函數. 把對象的內部狀態初始化成一致/ 有效的值無疑是更合理的方式.

這么做的原因是: 如果你沒有提供其它構造函數, 又沒有定義默認構造函數, 編譯器將為你自動

生成一個. 編譯器生成的構造函數并不會對對象進行合理的初始化.

如果你定義的類繼承現有類, 而你又沒有增加新的成員變量, 則不需要為新類定義默認構造函數.

3.3. ??????

Tip: 對單個參數的構造函數使用 C++ 關鍵字 explicit.

定義:

通常, 如果構造函數只有一個參數, 可看成是一種隱式轉換. 打個比方, 如果你定義了 Foo::Foo(string

name), 接著把一個字符串傳給一個以 Foo 對象為參數的函數, 構造函數 Foo::Foo(string name) 將被

調用, 并將該字符串轉換為一個 Foo 的臨時對象傳給調用函數. 看上去很方便, 但如果你并不希望如此

通過轉換生成一個新對象的話, 麻煩也隨之而來. 為避免構造函數被調用造成隱式轉換, 可以將其聲明為

explicit.

除單參數構造函數外, 這一規則也適用于除第一個參數以外的其他參數都具有默認參數的構造函數, 例如

Foo::Foo(string name, int id = 42).

優點:

避免不合時宜的變換.

缺點:

結論:

所有單參數構造函數都必須是顯式的. 在類定義中, 將關鍵字 explicit 加到單參數構造函數前: explicit

Foo(string name);

例外: 在極少數情況下, 拷貝構造函數可以不聲明成 explicit. 作為其它類的透明包裝器的類也是特例之

一. 類似的例外情況應在注釋中明確說明.

最后, 只有 std::initializer_list 的構造函數可以是非 explicit, 以允許你的類型結構可以使用列表初始化的

方式進行賦值. 例如:

MyType m = {1, 2};

MyType MakeMyType() { return {1, 2}; }

TakeMyType({1, 2});

3.4. ???????????

Tip: 如果你的類型需要, 就讓它們支持拷貝/ 移動. 否則, 就把隱式產生的拷貝和移動函數禁用.

定義:

可拷貝類型允許對象在初始化時得到來自相同類型的另一對象的值, 或在賦值時被賦予相同類型的另一對

象的值, 同時不改變源對象的值. 對于用戶定義的類型, 拷貝操作一般通過拷貝構造函數與拷貝賦值操作符

定義. string 類型就是一個可拷貝類型的例子.

可移動類型允許對象在初始化時得到來自相同類型的臨時對象的值, 或在賦值時被賦予相同類

型的臨時對象的值 (因此所有可拷貝對象也是可移動的). std::unique_ptr<int> 就是一個可移

動但不可復制的對象的例子. 對于用戶定義的類型, 移動操作一般是通過移動構造函數和移動賦

值操作符實現的.

拷貝/ 移動構造函數在某些情況下會被編譯器隱式調用. 例如, 通過傳值的方式傳遞對象.

優點:

可移動及可拷貝類型的對象可以通過傳值的方式進行傳遞或者返回, 這使得 API 更簡單, 更安全也更通用.

與傳指針和引用不同, 這樣的傳遞不會造成所有權, 生命周期, 可變性等方面的混亂, 也就沒必要在協議中

予以明確. 這同時也防止了客戶端與實現在非作用域內的交互, 使得它們更容易被理解與維護. 這樣的對

象可以和需要傳值操作的通用 API 一起使用, 例如大多數容器.

拷貝/ 移動構造函數與賦值操作一般來說要比它們的各種替代方案, 比如 Clone(), CopyFrom()

or Swap(), 更容易定義, 因為它們能通過編譯器產生, 無論是隱式的還是通過 = 默認. 這種方式

很簡潔, 也保證所有數據成員都會被復制. 拷貝與移動構造函數一般也更高效, 因為它們不需要

堆的分配或者是單獨的初始化和賦值步驟, 同時, 對于類似省略不必要的拷貝這樣的優化它們也

更加合適.

移動操作允許隱式且高效地將源數據轉移出右值對象. 這有時能讓代碼風格更加清晰.

缺點:

許多類型都不需要拷貝, 為它們提供拷貝操作會讓人迷惑, 也顯得荒謬而不合理. 為基類提供拷貝/ 賦值操

作是有害的, 因為在使用它們時會造成對象切割. 默認的或者隨意的拷貝操作實現可能是不正確的, 這往往

導致令人困惑并且難以診斷出的錯誤.

拷貝構造函數是隱式調用的, 也就是說, 這些調用很容易被忽略. 這會讓人迷惑, 尤其是對那些所

用的語言約定或強制要求傳引用的程序員來說更是如此. 同時, 這從一定程度上說會鼓勵過度拷

貝, 從而導致性能上的問題.

結論:

如果需要就讓你的類型可拷貝/ 可移動. 作為一個經驗法則, 如果對于你的用戶來說這個拷貝操作不是一

眼就能看出來的, 那就不要把類型設置為可拷貝. 如果讓類型可拷貝, 一定要同時給出拷貝構造函數和賦值

操作的定義. 如果讓類型可拷貝, 同時移動操作的效率高于拷貝操作, 那么就把移動的兩個操作 (移動構造

函數和賦值操作) 也給出定義. 如果類型不可拷貝, 但是移動操作的正確性對用戶顯然可見, 那么把這個類

型設置為只可移動并定義移動的兩個操作.

建議通過 = default 定義拷貝和移動操作. 定義非默認的移動操作目前需要異常. 時刻記得檢

測默認操作的正確性. 由于存在對象切割的風險, 不要為任何有可能有派生類的對象提供賦值操

作或者拷貝/ 移動構造函數 (當然也不要繼承有這樣的成員函數的類). 如果你的基類需要可復

制屬性, 請提供一個 public virtual Clone() 和一個 protected 的拷貝構造函數以供派生類

實現.

如果你的類不需要拷貝/ 移動操作, 請顯式地通過 = delete 或其他手段禁用之.

3.5. ?????????

Tip: 在能夠減少重復代碼的情況下使用委派和繼承構造函數.

定義:

委派和繼承構造函數是由 C++11 引進為了減少構造函數重復代碼而開發的兩種不同的特性. 通過特殊的

初始化列表語法, 委派構造函數允許類的一個構造函數調用其他的構造函數. 例如:

X::X(const string& name) : name_(name) {

...

}

X::X() : X("") { }

繼承構造函數允許派生類直接調用基類的構造函數, 一如繼承基類的其他成員函數, 而無需重新

聲明. 當基類擁有多個構造函數時這一功能尤其有用. 例如:

class Base {

public:

Base();

Base(int n);

Base(const string& s);

...

};

class Derived : public Base {

public:

using Base::Base; // Base's constructors are redeclared here.

};

如果派生類的構造函數只是調用基類的構造函數而沒有其他行為時, 這一功能特別有用.

優點:

委派和繼承構造函數可以減少冗余代碼, 提高可讀性. 委派構造函數對 Java 程序員來說并不陌生.

缺點:

使用輔助函數可以預估出委派構造函數的行為. 如果派生類和基類相比引入了新的成員變量, 繼承構造函

數就會讓人迷惑, 因為基類并不知道這些新的成員變量的存在.

結論:

只在能夠減少冗余代碼, 提高可讀性的前提下使用委派和繼承構造函數. 如果派生類有新的成員變量, 那么

使用繼承構造函數時要小心. 如果在派生類中對成員變量使用了類內部初始化的話, 繼承構造函數還是適

用的.

3.6. ??? VS. ?

Tip: 僅當只有數據時使用 struct, 其它一概使用 class.

說明:

在 C++ 中 struct 和 class 關鍵字幾乎含義一樣. 我們為這兩個關鍵字添加我們自己的語義理解, 以便未

定義的數據類型選擇合適的關鍵字.

struct 用來定義包含數據的被動式對象, 也可以包含相關的常量, 但除了存取數據成員之外, 沒有別的函數

功能. 并且存取功能是通過直接訪問位域, 而非函數調用. 除了構造函數, 析構函數, Initialize(), Reset(),

Validate() 等類似的函數外, 不能提供其它功能的函數.

如果需要更多的函數功能, class 更適合. 如果拿不準, 就用 class.

為了和 STL 保持一致, 對于仿函數和 trait 特性可以不用 class 而是使用 struct.

注意: 類和結構體的成員變量使用不同的命名規則.

3.7. ??

Tip: 使用組合 (composition, YuleFox 注: 這一點也是 GoF 在 <<Design Patterns>> 里反復強調的) 常常比使用

繼承更合理. 如果使用繼承的話, 定義為 public 繼承.

定義:

當子類繼承基類時, 子類包含了父基類所有數據及操作的定義. C++ 實踐中, 繼承主要用于兩種場合: 實

現繼承 (implementation inheritance), 子類繼承父類的實現代碼; 接口繼承 (interface inheritance), 子類僅

繼承父類的方法名稱.

優點:

實現繼承通過原封不動的復用基類代碼減少了代碼量. 由于繼承是在編譯時聲明, 程序員和編譯器都可以

理解相應操作并發現錯誤. 從編程角度而言, 接口繼承是用來強制類輸出特定的 API. 在類沒有實現 API

中某個必須的方法時, 編譯器同樣會發現并報告錯誤.

缺點:

對于實現繼承, 由于子類的實現代碼散布在父類和子類間之間, 要理解其實現變得更加困難. 子類不能重寫

父類的非虛函數, 當然也就不能修改其實現. 基類也可能定義了一些數據成員, 還要區分基類的實際布局.

結論:

所有繼承必須是 public 的. 如果你想使用私有繼承, 你應該替換成把基類的實例作為成員對象的方式.

不要過度使用實現繼承. 組合常常更合適一些. 盡量做到只在 “是一個” (“is-a”, YuleFox 注: 其他 “has-a”

情況下請使用組合) 的情況下使用繼承: 如果 Bar 的確 “是一種” Foo, Bar 才能繼承 Foo.

必要的話, 析構函數聲明為 virtual. 如果你的類有虛函數, 則析構函數也應該為虛函數. 注意數據成員在

任何情況下都必須是私有的.

當重載一個虛函數, 在衍生類中把它明確的聲明為 virtual. 理論依據: 如果省略 virtual 關鍵字, 代碼閱

讀者不得不檢查所有父類, 以判斷該函數是否是虛函數.

3.8. ????

Tip: 真正需要用到多重實現繼承的情況少之又少. 只在以下情況我們才允許多重繼承: 最多只有一個基類是非抽象

類; 其它基類都是以 Interface 為后綴的純接口類.

定義:

多重繼承允許子類擁有多個基類. 要將作為 純接口 的基類和具有 實現 的基類區別開來.

優點:

相比單繼承 (見繼承), 多重實現繼承可以復用更多的代碼.

缺點:

真正需要用到多重 實現 繼承的情況少之又少. 多重實現繼承看上去是不錯的解決方案, 但你通常也可以找

到一個更明確, 更清晰的不同解決方案.

結論:

只有當所有父類除第一個外都是純接口類 時, 才允許使用多重繼承. 為確保它們是純接口, 這些類必須以

Interface 為后綴.

Note: 關于該規則, Windows 下有個特例.

3.9. ??

Tip: 接口是指滿足特定條件的類, 這些類以 Interface 為后綴 (不強制).

定義:

當一個類滿足以下要求時, 稱之為純接口:

? 只有純虛函數 (“=0”) 和靜態函數 (除了下文提到的析構函數).

? 沒有非靜態數據成員.

? 沒有定義任何構造函數. 如果有, 也不能帶有參數, 并且必須為 protected.

? 如果它是一個子類, 也只能從滿足上述條件并以 Interface 為后綴的類繼承.

接口類不能被直接實例化, 因為它聲明了純虛函數. 為確保接口類的所有實現可被正確銷毀, 必須為之聲明

虛析構函數 (作為上述第 1 條規則的特例, 析構函數不能是純虛函數). 具體細節可參考 Stroustrup 的 The

C++ Programming Language, 3rd edition 第 12.4 節.

優點:

以 Interface 為后綴可以提醒其他人不要為該接口類增加函數實現或非靜態數據成員. 這一點對于多重

繼承 尤其重要. 另外, 對于 Java 程序員來說, 接口的概念已是深入人心.

缺點:

Interface 后綴增加了類名長度, 為閱讀和理解帶來不便. 同時,接口特性作為實現細節不應暴露給用戶.

結論:

只有在滿足上述需要時, 類才以 Interface 結尾, 但反過來, 滿足上述需要的類未必一定以 Interface 結

尾.

3.10. ?????

Tip: 除少數特定環境外,不要重載運算符.

定義:

一個類可以定義諸如 + 和 / 等運算符, 使其可以像內建類型一樣直接操作.

優點:

使代碼看上去更加直觀, 類表現的和內建類型 (如 int) 行為一致. 重載運算符使 Equals(), Add() 等函數

名黯然失色. 為了使一些模板函數正確工作, 你可能必須定義操作符.

缺點:

雖然操作符重載令代碼更加直觀, 但也有一些不足:

? 混淆視聽, 讓你誤以為一些耗時的操作和操作內建類型一樣輕巧.

? 更難定位重載運算符的調用點, 查找 Equals() 顯然比對應的 == 調用點要容易的多.

? 有的運算符可以對指針進行操作, 容易導致 bug. Foo + 4 做的是一件事, 而 &Foo + 4 可能做的是完

全不同的另一件事. 對于二者, 編譯器都不會報錯, 使其很難調試;

重載還有令你吃驚的副作用. 比如, 重載了 operator& 的類不能被前置聲明.

結論:

一般不要重載運算符. 尤其是賦值操作 (operator=) 比較詭異, 應避免重載. 如果需要的話, 可以定義類似

Equals(), CopyFrom() 等函數.

然而, 極少數情況下可能需要重載運算符以便與模板或 “標準” C++ 類互操作 (如 operator<<(ostream&,

const T&)). 只有被證明是完全合理的才能重載, 但你還是要盡可能避免這樣做. 尤其是不要僅僅為了在

STL 容器中用作鍵值就重載 operator== 或 operator<; 相反, 你應該在聲明容器的時候, 創建相等判斷和

大小比較的仿函數類型.

有些 STL 算法確實需要重載 operator== 時, 你可以這么做, 記得別忘了在文檔中說明原因.

參考拷貝構造函數 和函數重載.

3.11. ????

Tip: 將 所有 數據成員聲明為 private, 并根據需要提供相應的存取函數. 例如, 某個名為 foo_ 的變量, 其取值函數

是 foo(). 還可能需要一個賦值函數 set_foo().

特例是, 靜態常量數據成員 (一般寫做 kFoo) 不需要是私有成員.

一般在頭文件中把存取函數定義成內聯函數.

參考繼承 和函數命名

3.11. ??亰?

Tip: 在類中使用特定的聲明順序: public: 在 private: 之前, 成員函數在數據成員 (變量) 前;

類的訪問控制區段的聲明順序依次為: public:, protected:, private:. 如果某區段沒內容, 可以不聲明.

每個區段內的聲明通常按以下順序:

? typedefs 和枚舉

? 常量

? 構造函數

? 析構函數

? 成員函數, 含靜態成員函數

? 數據成員, 含靜態數據成員

友元聲明應該放在 private 區段. 如果用宏 DISALLOW_COPY_AND_ASSIGN 禁用拷貝和賦值, 應當將其置

于 private 區段的末尾, 也即整個類聲明的末尾. 參見可拷貝類型和可移動類型.

.cc 文件中函數的定義應盡可能和聲明順序一致.

不要在類定義中內聯大型函數. 通常, 只有那些沒有特別意義或性能要求高, 并且是比較短小的函數才能被定義為

內聯函數. 更多細節參考內聯函數.

3.12. 編?????

Tip: 傾向編寫簡短, 凝練的函數.

我們承認長函數有時是合理的, 因此并不硬性限制函數的長度. 如果函數超過 40 行, 可以思索一下能不能在不影

響程序結構的前提下對其進行分割.

即使一個長函數現在工作的非常好, 一旦有人對其修改, 有可能出現新的問題. 甚至導致難以發現的 bug. 使函數

盡量簡短, 便于他人閱讀和修改代碼.

在處理代碼時, 你可能會發現復雜的長函數. 不要害怕修改現有代碼: 如果證實這些代碼使用/ 調試困難, 或者你

需要使用其中的一小段代碼, 考慮將其分割為更加簡短并易于管理的若干函數.

??

(YuleFox) ??

1. 不在構造函數中做太多邏輯相關的初始化;

2. 編譯器提供的默認構造函數不會對變量進行初始化, 如果定義了其他構造函數, 編譯器不再提供, 需要編碼者自

行提供默認構造函數;

3. 為避免隱式轉換, 需將單參數構造函數聲明為 explicit;

4. 為避免拷貝構造函數, 賦值操作的濫用和編譯器自動生成, 可將其聲明為 private 且無需實現;

5. 僅在作為數據集合時使用 struct;

6. 組合 > 實現繼承 > 接口繼承 > 私有繼承, 子類重載的虛函數也要聲明 virtual 關鍵字, 雖然編譯器允許不這

樣做;

7. 避免使用多重繼承, 使用時, 除一個基類含有實現外, 其他基類均為純接口;

8. 接口類類名以 Interface 為后綴, 除提供帶實現的虛析構函數, 靜態成員函數外, 其他均為純虛函數, 不定義非

靜態數據成員, 不提供構造函數, 提供的話,聲明為 protected;

9. 為降低復雜性, 盡量不重載操作符, 模板, 標準類中使用時提供文檔說明;

10. 存取函數一般內聯在頭文件中;

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

12. 函數體盡量短小, 緊湊, 功能單一;

4. ?? Google ???

Google 用了很多自己實現的技巧/ 工具使 C++ 代碼更加健壯, 我們使用 C++ 的方式可能和你在其它地方見到

的有所不同.

4.1. ???ф??指?

Tip: 動態分配出的對象最好有單一且固定的所有主(onwer), 且通過智能指針傳遞所有權(ownership).

定義:

所有權是一種登記/管理動態內存和其它資源的技術。動態分配出的對象的所有主是一個對象或函數,后

者負責確保當前者無用時就自動銷毀前者。所有權有時可以共享,那么就由最后一個所有主來負責銷毀

它。甚至也可以不用共享,在代碼中直接把所有權傳遞給其它對象。

其實您可以把智能指針當成一個重載了 * 和 -> 的「對象」來看。智能指針類型被用來自動化所有權的登

記工作,來確保執行銷毀義務到位。std::unique_ptr 是 C++11 新推出的一種智能指針類型,用來表示

動態分配出的對象的「獨一無二」所有權;當 std::unique_ptr 離開作用域,對象就會被銷毀。不能復

制 std::unique_ptr, 但可以把它移動(move)給新所有主。std::shared_ptr 同樣表示動態分配對象的

所有權,但可以被共享,也可以被復制;對象的所有權由所有復制者共同擁有,最后一個復制者被銷毀

時,對象也會隨著被銷毀。

優點:

? 如果沒有清晰、邏輯條理的所有權安排,不可能管理好動態分配的內存。

? 傳遞對象的所有權,開銷比復制來得小,如果可以復制的話。

? 傳遞所有權也比「借用」指針或引用來得簡單,畢竟它大大省去了兩個用戶一起協調對象生命周期的工作。

? 如果所有權邏輯條理,有文檔且不亂來的話,可讀性很棒。

? 可以不用手動完成所有權的登記工作,大大簡化了代碼,也免去了一大波錯誤之惱。

? 對于 const 對象來說,智能指針簡單易用,也比深度復制高效。

缺點:

? 不得不用指針(不管是智能的還是原生的)來表示和傳遞所有權。指針語義可要比值語義復雜得許多了,特別

是在 API 里:您不光要操心所有權,還要顧及別名,生命周期,可變性(mutability)以及其它大大小小問題。

? 其實值語義的開銷經常被高估,所以就所有權的性能來說,可不能光只考慮可讀性以及復雜性。

? 如果 API 依賴所有權的傳遞,就會害得客戶端不得不用單一的內存管理模型。

? 銷毀資源并回收的相關代碼不是很明朗。

? std::unique_ptr 的所有權傳遞原理是 C++11 的 move 語法,后者畢竟是剛剛推出的,容易迷惑程序員。

? 如果原本的所有權設計已經夠完善了,那么若要引入所有權共享機制,可能不得不重構整個系統。

? 所有權共享機制的登記工作在運行時進行,開銷可能相當不小。

? 某些極端情況下,所有權被共享的對象永遠不會被銷毀,比如引用死循環(cyclic references)。

? 智能指針并不能夠完全代替原生指針。

決定:

如果必須使用動態分配,傾向于保持分配者的所有權。如果其他地方要使用這個對象,最好傳遞它的拷

貝,或者傳遞一個不用改變所有權的指針或引用。傾向于使用 std::unique_ptr 來明確所有權傳遞,例

如:

std::unique_ptr<Foo> FooFactory();

void FooConsumer(std::unique_ptr<Foo> ptr);

避 免 使 用 共 享 所 有 權。 如 果 對 性 能 要 求 很 高, 并 且 操 作 的 對 象 是 不 可 變 的 (比 如 說

std::shared_ptr<const Foo> ),這時可以用共享所有權來避免昂貴的拷貝操作。如果確實要使用

共享所有權,傾向于使用 std::shared_ptr 。

不要在新代碼中使用 scoped_ptr `` ,除非你必須兼容老版本的 C++。總是用 ``std::unique_ptr 代

替 std::auto_ptr 。

4.2. cpplint

Tip: 使用 cpplint.py 檢查風格錯誤.

cpplint.py 是一個用來分析源文件, 能檢查出多種風格錯誤的工具. 它不并完美, 甚至還會漏報和誤報, 但它仍

然是一個非常有用的工具. 在行尾加 // NOLINT, 或在上一行加 // NOLINTNEXTLINE, 可以忽略報錯。

某些項目會指導你如何使用他們的項目工具運行 cpplint.py. 如果你參與的項目沒有提供, 你可以單獨下載

cpplint.py.

??δ

acgtyrantε??

1. 把智能指針當成對象來看待的話,就很好領會它與所指對象之間的關系了。

2. 原來 Rust 的 Ownership 思想是受到了 C++ 智能指針的很大啟發啊。

3. scoped_ptr 和 auto_ptr 已過時。現在是 shared_ptr 和 uniqued_ptr 的天下了。

4. 按本文來說,似乎除了智能指針,還有其它所有權機制,值得留意。

5. Arch Linux 用戶注意了,AUR 有對 cpplint 打包。

5. ?? C++ ??

5.1. ????

Tip: 所有按引用傳遞的參數必須加上 const.

定義:

在 C 語言中, 如果函數需要修改變量的值, 參數必須為指針, 如 int foo(int *pval). 在 C++ 中, 函數

還可以聲明引用參數: int foo(int &val).

優點:

定義引用參數防止出現 (*pval)++ 這樣丑陋的代碼. 像拷貝構造函數這樣的應用也是必需的. 而且更明

確, 不接受 NULL 指針.

缺點:

容易引起誤解, 因為引用在語法上是值變量卻擁有指針的語義.

結論:

函數參數列表中, 所有引用參數都必須是 const:

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

事實上這在 Google Code 是一個硬性約定: 輸入參數是值參或 const 引用, 輸出參數為指針. 輸入參數可

以是 const 指針, 但決不能是非 const 的引用參數,除非用于交換,比如 swap().

有時候,在輸入形參中用 const T* 指針比 const T& 更明智。比如:

? 您會傳 null 指針。

? 函數要把指針或對地址的引用賦值給輸入形參。

總之大多時候輸入形參往往是 const T&. 若用 const T* 說明輸入另有處理。所以若您要用 const T*, 則

應有理有據,否則會害得讀者誤解。

5.2. ????

Tip: 只在定義移動構造函數與移動賦值操作時使用右值引用. 不要使用 std::forward.

定義:

右值引用是一種只能綁定到臨時對象的引用的一種, 其語法與傳統的引用語法相似. 例如, void

f(string&& s); 聲明了一個其參數是一個字符串的右值引用的函數.

優點:

用于定義移動構造函數 (使用類的右值引用進行構造的函數) 使得移動一個值而非拷貝之成為可能. 例如,

如果 v1 是一個 vector<string>, 則 auto v2(std::move(v1)) 將很可能不再進行大量的數據復制而只

是簡單地進行指針操作, 在某些情況下這將帶來大幅度的性能提升.

右值引用使得編寫通用的函數封裝來轉發其參數到另外一個函數成為可能, 無論其參數是否是臨時對象都

能正常工作.

右值引用能實現可移動但不可拷貝的類型, 這一特性對那些在拷貝方面沒有實際需求, 但有時又需要將它

們作為函數參數傳遞或塞入容器的類型很有用.

要高效率地使用某些標準庫類型, 例如 std::unique_ptr, std::move 是必需的.

缺點:

右值引用是一個相對比較新的特性 (由 C++11 引入), 它尚未被廣泛理解. 類似引用崩潰, 移動構造函數的

自動推導這樣的規則都是很復雜的.

結論:

只在定義移動構造函數與移動賦值操作時使用右值引用, 不要使用 std::forward 功能函數. 你可能會使

用 std::move 來表示將值從一個對象移動而不是復制到另一個對象.

5.3. ????

Tip: 若要用好函數重載,最好能讓讀者一看調用點(call site)就胸有成竹,不用花心思猜測調用的重載函數到底

是哪一種。該規則適用于構造函數。

定義:

你可以編寫一個參數類型為 const string& 的函數, 然后用另一個參數類型為 const char* 的函數重載

它:

class MyClass {

public:

void Analyze(const string &text);

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

};

優點:

通過重載參數不同的同名函數, 令代碼更加直觀. 模板化代碼需要重載, 同時為使用者帶來便利.

缺點:

如果函數單單靠不同的參數類型而重載(acgtyrant 注:這意味著參數數量不變),讀者就得十分熟悉

C++ 五花八門的匹配規則,以了解匹配過程具體到底如何。另外,當派生類只重載了某個函數的部分變

體,繼承語義容易令人困惑。

結論:

如果您打算重載一個函數, 可以試試改在函數名里加上參數信息。例如,用 AppendString() 和

AppendInt() 等,而不是一口氣重載多個 Append().

5.4. ????

Tip: 我們不允許使用缺省函數參數,少數極端情況除外。盡可能改用函數重載。

優點:

當您有依賴缺省參數的函數時,您也許偶爾會修改修改這些缺省參數。通過缺省參數,不用再為個別情況

而特意定義一大堆函數了。與函數重載相比,缺省參數語法更為清晰,代碼少,也很好地區分了「必選參

數」和「可選參數」。

缺點:

缺省參數會干擾函數指針,害得后者的函數簽名(function signature)往往對不上所實際要調用的函數簽

名。即在一個現有函數添加缺省參數,就會改變它的類型,那么調用其地址的代碼可能會出錯,不過函數

重載就沒這問題了。此外,缺省參數會造成臃腫的代碼,畢竟它們在每一個調用點(call site)都有重復

(acgtyrant 注:我猜可能是因為調用函數的代碼表面上看來省去了不少參數,但編譯器在編譯時還是會

在每一個調用代碼里統統補上所有默認實參信息,造成大量的重復)。函數重載正好相反,畢竟它們所謂

的「缺省參數」只會出現在函數定義里。

結論:

由于缺點并不是很嚴重,有些人依舊偏愛缺省參數勝于函數重載。所以除了以下情況,我們要求必須顯式

提供所有參數(acgtyrant 注:即不能再通過缺省參數來省略參數了)。

其一,位于 .cc 文件里的靜態函數或匿名空間函數,畢竟都只能在局部文件里調用該函數了。

其二,可以在構造函數里用缺省參數,畢竟不可能取得它們的地址。

其三,可以用來模擬變長數組。

// 通過空 AlphaNum 以支持四個形參

string StrCat(const AlphaNum &a,

const AlphaNum &b = gEmptyAlphaNum,

const AlphaNum &c = gEmptyAlphaNum,

const AlphaNum &d = gEmptyAlphaNum);

5.5. ????? alloca()

Tip: 我們不允許使用變長數組和 alloca().

優點:

變長數組具有渾然天成的語法. 變長數組和 alloca() 也都很高效.

缺點:

變長數組和 alloca() 不是標準 C++ 的組成部分. 更重要的是, 它們根據數據大小動態分配堆棧內存, 會

引起難以發現的內存越界 bugs: “在我的機器上運行的好好的, 發布后卻莫名其妙的掛掉了”.

結論:

改用更安全的分配器(allocator),就像 std::vector 或 std::unique_ptr<T[]>.

5.6. ??

Tip: 我們允許合理的使用友元類及友元函數.

通常友元應該定義在同一文件內, 避免代碼讀者跑到其它文件查找使用該私有成員的類. 經常用到友元的一個地

方是將 FooBuilder 聲明為 Foo 的友元, 以便 FooBuilder 正確構造 Foo 的內部狀態, 而無需將該狀態暴露出來. 某

些情況下, 將一個單元測試類聲明成待測類的友元會很方便.

友元擴大了 (但沒有打破) 類的封裝邊界. 某些情況下, 相對于將類成員聲明為 public, 使用友元是更好的選擇,

尤其是如果你只允許另一個類訪問該類的私有成員時. 當然, 大多數類都只應該通過其提供的公有成員進行互操作.

5.7. ??

Tip: 我們不使用 C++ 異常.

優點:

? 異常允許應用高層決定如何處理在底層嵌套函數中「不可能發生」的失敗(failures),不用管那些含糊且容易

出錯的錯誤代碼(acgtyrant 注:error code, 我猜是C語言函數返回的非零 int 值)。

? 很多現代語言都用異常。引入異常使得 C++ 與 Python, Java 以及其它類 C++ 的語言更一脈相承。

? 有些第三方 C++ 庫依賴異常,禁用異常就不好用了。

? 異常是處理構造函數失敗的唯一途徑。雖然可以用工廠函數(acgtyrant 注:factory function, 出自 C++ 的一

種設計模式,即「簡單工廠模式」)或 Init() 方法代替異常, 但是前者要求在堆棧分配內存,后者會導致剛創

建的實例處于”無效“狀態。

? 在測試框架里很好用。

缺點:

? 在現有函數中添加 throw 語句時,您必須檢查所有調用點。要么讓所有調用點統統具備最低限度的異常安全保

證,要么眼睜睜地看異常一路歡快地往上跑,最終中斷掉整個程序。舉例,f() 調用 g(), g() 又調用 h(), 且

h 拋出的異常被 f 捕獲。當心 g, 否則會沒妥善清理好。

? 還有更常見的,異常會徹底擾亂程序的執行流程并難以判斷,函數也許會在您意料不到的地方返回。您或許會

加一大堆何時何處處理異常的規定來降低風險,然而開發者的記憶負擔更重了。

? 異常安全需要 RAII 和不同的編碼實踐. 要輕松編寫出正確的異常安全代碼需要大量的支持機制. 更進一步地

說, 為了避免讀者理解整個調用表, 異常安全必須隔絕從持續狀態寫到 “提交” 狀態的邏輯. 這一點有利有弊 (因

為你也許不得不為了隔離提交而混淆代碼). 如果允許使用異常, 我們就不得不時刻關注這樣的弊端, 即使有時它

們并不值得.

? 啟用異常會增加二進制文件數據,延長編譯時間(或許影響小),還可能加大地址空間的壓力。

? 濫用異常會變相鼓勵開發者去捕捉不合時宜,或本來就已經沒法恢復的「偽異常」。比如,用戶的輸入不符合格

式要求時,也用不著拋異常。如此之類的偽異常列都列不完。

結論:

從表面上看來,使用異常利大于弊, 尤其是在新項目中. 但是對于現有代碼, 引入異常會牽連到所有相關代

碼. 如果新項目允許異常向外擴散, 在跟以前未使用異常的代碼整合時也將是個麻煩. 因為 Google 現有的

大多數 C++ 代碼都沒有異常處理, 引入帶有異常處理的新代碼相當困難.

鑒于 Google 現有代碼不接受異常, 在現有代碼中使用異常比在新項目中使用的代價多少要大一些. 遷移過

程比較慢, 也容易出錯. 我們不相信異常的使用有效替代方案, 如錯誤代碼, 斷言等會造成嚴重負擔.

我們并不是基于哲學或道德層面反對使用異常, 而是在實踐的基礎上. 我們希望在 Google 使用我們自己的

開源項目, 但項目中使用異常會為此帶來不便, 因此我們也建議不要在 Google 的開源項目中使用異常. 如

果我們需要把這些項目推倒重來顯然不太現實.

對于 Windows 代碼來說, 有個特例.

(YuleFox 注: 對于異常處理, 顯然不是短短幾句話能夠說清楚的, 以構造函數為例, 很多 C++ 書籍上都提到當構

造失敗時只有異常可以處理, Google 禁止使用異常這一點, 僅僅是為了自身的方便, 說大了, 無非是基于軟件管理成本

上, 實際使用中還是自己決定)

5.8. ???????

TODO

Tip: 我們禁止使用 RTTI.

定義:

RTTI 允許程序員在運行時識別 C++ 類對象的類型. 它通過使用 typeid 或者 dynamic_cast 完成.

優點:

RTTI 的標準替代 (下面將描述) 需要對有問題的類層級進行修改或重構. 有時這樣的修改并不是我們所想

要的, 甚至是不可取的, 尤其是在一個已經廣泛使用的或者成熟的代碼中.

RTTI 在某些單元測試中非常有用. 比如進行工廠類測試時, 用來驗證一個新建對象是否為期望的動態類

型. RTTI 對于管理對象和派生對象的關系也很有用.

在考慮多個抽象對象時 RTTI 也很好用. 例如:

bool Base::Equal(Base* other) = 0;

bool Derived::Equal(Base* other) {

Derived* that = dynamic_cast<Derived*>(other);

if (that == NULL)

return false;

...

}

缺點:

在運行時判斷類型通常意味著設計問題. 如果你需要在運行期間確定一個對象的類型, 這通常說明你需要

考慮重新設計你的類.

隨意地使用 RTTI 會使你的代碼難以維護. 它使得基于類型的判斷樹或者 switch 語句散布在代碼各處. 如

果以后要進行修改, 你就必須檢查它們.

結論:

RTTI 有合理的用途但是容易被濫用, 因此在使用時請務必注意. 在單元測試中可以使用 RTTI, 但是在其

他代碼中請盡量避免. 尤其是在新代碼中, 使用 RTTI 前務必三思. 如果你的代碼需要根據不同的對象類

型執行不同的行為的話, 請考慮用以下的兩種替代方案之一查詢類型:

虛函數可以根據子類類型的不同而執行不同代碼. 這是把工作交給了對象本身去處理.

如果這一工作需要在對象之外完成, 可以考慮使用雙重分發的方案, 例如使用訪問者設計模式. 這就能夠在

對象之外進行類型判斷.

如果程序能夠保證給定的基類實例實際上都是某個派生類的實例, 那么就可以自由使用 dynamic_cast. 在

這種情況下, 使用 dynamic_cast 也是一種替代方案.

基于類型的判斷樹是一個很強的暗示, 它說明你的代碼已經偏離正軌了. 不要像下面這樣:

if (typeid(*data) == typeid(D1)) {

...

} else if (typeid(*data) == typeid(D2)) {

...

} else if (typeid(*data) == typeid(D3)) {

...

一旦在類層級中加入新的子類, 像這樣的代碼往往會崩潰. 而且, 一旦某個子類的屬性改變了, 你很難找到

并修改所有受影響的代碼塊.

不要去手工實現一個類似 RTTI 的方案. 反對 RTTI 的理由同樣適用于這些方案, 比如帶類型標簽的類繼

承體系. 而且, 這些方案會掩蓋你的真實意圖.

5.9. ????

Tip: 使用 C++ 的類型轉換, 如 static_cast<>(). 不要使用 int y = (int)x 或 int y = int(x) 等轉換方式;

定義:

C++ 采用了有別于 C 的類型轉換機制, 對轉換操作進行歸類.

優點:

C 語言的類型轉換問題在于模棱兩可的操作; 有時是在做強制轉換 (如 (int)3.5), 有時是在做類型轉換

(如 (int)"hello"). 另外, C++ 的類型轉換在查找時更醒目.

缺點:

惡心的語法.

結論:

不要使用 C 風格類型轉換. 而應該使用 C++ 風格.

? 用 static_cast 替代 C 風格的值轉換, 或某個類指針需要明確的向上轉換為父類指針時.

? 用 const_cast 去掉 const 限定符.

? 用 reinterpret_cast 指針類型和整型或其它指針之間進行不安全的相互轉換. 僅在你對所做一切了

然于心時使用.

至于 dynamic_cast 參見5.8. 運行時類型識別.

5.10. ?

Tip: 只在記錄日志時使用流.

定義:

流用來替代 printf() 和 scanf().

優點:

有了流, 在打印時不需要關心對象的類型. 不用擔心格式化字符串與參數列表不匹配 (雖然在 gcc 中使用

printf 也不存在這個問題). 流的構造和析構函數會自動打開和關閉對應的文件.

缺點:

流使得 pread() 等功能函數很難執行. 如果不使用 printf 風格的格式化字符串, 某些格式化操作 (尤其

是常用的格式字符串 %.*s) 用流處理性能是很低的. 流不支持字符串操作符重新排序 (%1s), 而這一點對

于軟件國際化很有用.

結論:

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

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

拓展討論:

對這一條規則存在一些爭論, 這兒給出點深層次原因. 回想一下唯一性原則 (Only One Way): 我們希望在

任何時候都只使用一種確定的 I/O 類型, 使代碼在所有 I/O 處都保持一致. 因此, 我們不希望用戶來決定

是使用流還是 printf + read/write. 相反, 我們應該決定到底用哪一種方式. 把日志作為特例是因為日

志是一個非常獨特的應用, 還有一些是歷史原因.

流的支持者們主張流是不二之選, 但觀點并不是那么清晰有力. 他們指出的流的每個優勢也都是其劣勢.

流最大的優勢是在輸出時不需要關心打印對象的類型. 這是一個亮點. 同時, 也是一個不足: 你很容易用錯

類型, 而編譯器不會報警. 使用流時容易造成的這類錯誤:

cout << this; // 輸出地址

cout << *this; // 輸出值

由于 << 被重載, 編譯器不會報錯. 就因為這一點我們反對使用操作符重載.

有人說 printf 的格式化丑陋不堪, 易讀性差, 但流也好不到哪兒去. 看看下面兩段代碼吧, 實現相同的功

能, 哪個更清晰?

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));

你可能會說, “把流封裝一下就會比較好了”, 這兒可以, 其他地方呢? 而且不要忘了, 我們的目標是使語言

更緊湊, 而不是添加一些別人需要學習的新裝備.

每一種方式都是各有利弊, “沒有最好, 只有更適合”. 簡單性原則告誡我們必須從中選擇其一, 最后大多數

決定采用 printf + read/write.

5.11. ???????

Tip: 對于迭代器和其他模板對象使用前綴形式 (++i) 的自增, 自減運算符.

定義:

對于變量在自增 (++i 或 i++) 或自減 (--i 或 i--) 后表達式的值又沒有沒用到的情況下, 需要確定到底是

使用前置還是后置的自增 (自減).

優點:

不考慮返回值的話, 前置自增 (++i) 通常要比后置自增 (i++) 效率更高. 因為后置自增 (或自減) 需要對表

達式的值 i 進行一次拷貝. 如果 i 是迭代器或其他非數值類型, 拷貝的代價是比較大的. 既然兩種自增方

式實現的功能一樣, 為什么不總是使用前置自增呢?

缺點:

在 C 開發中, 當表達式的值未被使用時, 傳統的做法是使用后置自增, 特別是在 for 循環中. 有些人覺得

后置自增更加易懂, 因為這很像自然語言, 主語 (i) 在謂語動詞 (++) 前.

結論:

對簡單數值 (非對象), 兩種都無所謂. 對迭代器和模板類型, 使用前置自增 (自減).

5.12. const ??

Tip: 我們強烈建議你在任何可能的情況下都要使用 const. 此外有時改用 C++11 推出的 constexpr 更好。

定義:

在聲明的變量或參數前加上關鍵字 const 用于指明變量值不可被篡改 (如 const int foo ). 為類中的函

數加上 const 限定符表明該函數不會修改類成員變量的狀態 (如 class Foo { int Bar(char c) const;

};).

優點:

大家更容易理解如何使用變量. 編譯器可以更好地進行類型檢測, 相應地, 也能生成更好的代碼. 人們對編

寫正確的代碼更加自信, 因為他們知道所調用的函數被限定了能或不能修改變量值. 即使是在無鎖的多線

程編程中, 人們也知道什么樣的函數是安全的.

缺點:

const 是入侵性的: 如果你向一個函數傳入 const 變量, 函數原型聲明中也必須對應 const 參數 (否則變

量需要 const_cast 類型轉換), 在調用庫函數時顯得尤其麻煩.

結論:

const 變量, 數據成員, 函數和參數為編譯時類型檢測增加了一層保障; 便于盡早發現錯誤. 因此, 我們強

烈建議在任何可能的情況下使用 const:

? 如果函數不會修改傳你入的引用或指針類型參數, 該參數應聲明為 const.

? 盡可能將函數聲明為 const. 訪問函數應該總是 const. 其他不會修改任何數據成員, 未調用非 const

函數, 不會返回數據成員非 const 指針或引用的函數也應該聲明成 const.

? 如果數據成員在對象構造之后不再發生變化, 可將其定義為 const.

然而, 也不要發了瘋似的使用 const. 像 const int * const * const x; 就有些過了, 雖然它非常精確

的描述了常量 x. 關注真正有幫助意義的信息: 前面的例子寫成 const int** x 就夠了.

關鍵字 mutable 可以使用, 但是在多線程中是不安全的, 使用時首先要考慮線程安全.

const 的位置:

有人喜歡 int const *foo 形式, 不喜歡 const int* foo, 他們認為前者更一致因此可讀性也更好: 遵循

了 const 總位于其描述的對象之后的原則. 但是一致性原則不適用于此, “不要過度使用” 的聲明可以取消

大部分你原本想保持的一致性. 將 const 放在前面才更易讀, 因為在自然語言中形容詞 (const) 是在名詞

(int) 之前.

這是說, 我們提倡但不強制 const 在前. 但要保持代碼的一致性! (Yang.Y 注: 也就是不要在一些地方把

const 寫在類型前面, 在其他地方又寫在后面, 確定一種寫法, 然后保持一致.)

5.13. constexpr ??

Tip: 在 C++11 里,用 constexpr 來定義真正的常量,或實現常量初始化。

定義:

變量可以被聲明成 constexpr 以表示它是真正意義上的常量,即在編譯時和運行時都不變。函數或構造函

數也可以被聲明成 constexpr, 以用來定義 constexpr 變量。

優點:

如今 constexpr 就可以定義浮點式的真常量,不用再依賴字面值了;也可以定義用戶自定義類型上的常

量;甚至也可以定義函數調用所返回的常量。

缺點:

若過早把變量優化成 constexpr 變量,將來又要把它改為常規變量時,挺麻煩的;當前對 constexpr 函數

和構造函數中允許的限制可能會導致這些定義中解決的方法模糊。

結論:

靠 constexpr 特性,方才實現了 C++ 在接口上打造真正常量機制的可能。好好用 constexpr 來定義真

常量以及支持常量的函數。避免復雜的函數定義,以使其能夠與 constexpr 一起使用。千萬別癡心妄想地

想靠 constexpr 來強制代碼「內聯」。

5.14. ??

Tip: C++ 內建整型中, 僅使用 int. 如果程序中需要不同大小的變量, 可以使用 <stdint.h> 中長度精確的整型, 如

int16_t. 如果您的變量可能不小于 2^31 (2GiB), 就用 64 位變量比如 int64_t. 此外要留意,哪怕您的值并不會超出

int 所能夠表示的范圍,在計算過程中也可能會溢出。所以拿不準時,干脆用更大的類型。

定義:

C++ 沒有指定整型的大小. 通常人們假定 short 是 16 位, int 是 32 位, long 是 32 位, long long 是 64

位.

優點:

保持聲明統一.

缺點:

C++ 中整型大小因編譯器和體系結構的不同而不同.

結論:

<stdint.h> 定義了 int16_t, uint32_t, int64_t 等整型, 在需要確保整型大小時可以使用它們代替

short, unsigned long long 等. 在 C 整型中, 只使用 int. 在合適的情況下, 推薦使用標準類型如 size_t

和 ptrdiff_t.

如果已知整數不會太大, 我們常常會使用 int, 如循環計數. 在類似的情況下使用原生類型 int. 你可以認

為 int 至少為 32 位, 但不要認為它會多于 32 位. 如果需要 64 位整型, 用 int64_t 或 uint64_t.

對于大整數, 使用 int64_t.

不要使用 uint32_t 等無符號整型, 除非你是在表示一個位組而不是一個數值, 或是你需要定義二進制補

碼溢出. 尤其是不要為了指出數值永不會為負, 而使用無符號類型. 相反, 你應該使用斷言來保護數據.

如果您的代碼涉及容器返回的大小(size),確保其類型足以應付容器各種可能的用法。拿不準時,類型

越大越好。

小心整型類型轉換和整型提升(acgtyrant 注:integer promotions, 比如 int 與 unsigned int 運算時,

前者被提升為 unsigned int 而有可能溢出),總有意想不到的后果。

關于無符號整數:

有些人, 包括一些教科書作者, 推薦使用無符號類型表示非負數. 這種做法試圖達到自我文檔化. 但是, 在

C 語言中, 這一優點被由其導致的 bug 所淹沒. 看看下面的例子:

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

上述循環永遠不會退出! 有時 gcc 會發現該 bug 并報警, 但大部分情況下都不會. 類似的 bug 還會出現在

比較有符合變量和無符號變量時. 主要是 C 的類型提升機制會致使無符號類型的行為出乎你的意料.

因此, 使用斷言來指出變量為非負數, 而不是使用無符號型!

5.15. 64 ?с?????

Tip: 代碼應該對 64 位和 32 位系統友好. 處理打印, 比較, 結構體對齊時應切記:

? 對于某些類型, printf() 的指示符在 32 位和 64 位系統上可移植性不是很好. C99 標準定義了一些可移植的格

式化指示符. 不幸的是, MSVC 7.1 并非全部支持, 而且標準中也有所遺漏, 所以有時我們不得不自己定義一個丑

陋的版本 (頭文件 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

注意 PRI* 宏會被編譯器擴展為獨立字符串. 因此如果使用非常量的格式化字符串, 需要將宏的

值而不是宏名插入格式中. 使用 PRI* 宏同樣可以在 % 后包含長度指示符. 例如, printf("x =

%30"PRIuS"\n", x) 在 32 位 Linux 上將被展開為 printf("x = %30" "u" "\n", x), 編譯器當成

printf("x = %30u\n", x) 處理 (Yang.Y 注: 這在 MSVC 6.0 上行不通, VC 6 編譯器不會自動把引

號間隔的多個字符串連接一個長字符串).

? 記住 sizeof(void *) != sizeof(int). 如果需要一個指針大小的整數要用 intptr_t.

? 你要非常小心的對待結構體對齊, 尤其是要持久化到磁盤上的結構體 (Yang.Y 注: 持久化 - 將數據按字節流順序

保存在磁盤文件或數據庫中). 在 64 位系統中, 任何含有 int64_t/uint64_t 成員的類/結構體, 缺省都以 8 字節

在結尾對齊. 如果 32 位和 64 位代碼要共用持久化的結構體, 需要確保兩種體系結構下的結構體對齊一致. 大多

數編譯器都允許調整結構體對齊. gcc 中可使用 __attribute__((packed)). MSVC 則提供了 #pragma pack()

和 __declspec(align()) (YuleFox 注, 解決方案的項目屬性里也可以直接設置).

? 創建 64 位常量時使用 LL 或 ULL 作為后綴, 如:

int64_t my_value = 0×123456789LL;

uint64_t my_mask = 3ULL << 48;

? 如果你確實需要 32 位和 64 位系統具有不同代碼, 可以使用 #ifdef _LP64 指令來切分 32/64 位代碼. (盡量不

要這么做, 如果非用不可, 盡量使修改局部化)

5.16. 人???

Tip: 使用宏時要非常謹慎, 盡量以內聯函數, 枚舉和常量代替之.

宏意味著你和編譯器看到的代碼是不同的. 這可能會導致異常行為, 尤其因為宏具有全局作用域.

值得慶幸的是, C++ 中, 宏不像在 C 中那么必不可少. 以往用宏展開性能關鍵的代碼, 現在可以用內聯函數替代.

用宏表示常量可被 const 變量代替. 用宏 “縮寫” 長變量名可被引用代替. 用宏進行條件編譯... 這個, 千萬別這么做,

會令測試更加痛苦 (#define 防止頭文件重包含當然是個特例).

宏可以做一些其他技術無法實現的事情, 在一些代碼庫 (尤其是底層庫中) 可以看到宏的某些特性 (如用 # 字符串

化, 用 ## 連接等等). 但在使用前, 仔細考慮一下能不能不使用宏達到同樣的目的.

下面給出的用法模式可以避免使用宏帶來的問題; 如果你要宏, 盡可能遵守:

? 不要在 .h 文件中定義宏.

? 在馬上要使用時才進行 #define, 使用后要立即 #undef.

? 不要只是對已經存在的宏使用 #undef,選擇一個不會沖突的名稱;

? 不要試圖使用展開后會導致 C++ 構造不穩定的宏, 不然也至少要附上文檔說明其行為.

? 不要用 ## 處理函數,類和變量的名字。

5.17. 0, nullptr ? NULL

Tip: 整數用 0, 實數用 0.0, 指針用 nullptr 或 NULL, 字符 (串) 用 '\0'.

整數用 0, 實數用 0.0, 這一點是毫無爭議的.

對于指針 (地址值), 到底是用 0, NULL 還是 nullptr. C++11 項目用 nullptr; C++03 項目則用 NULL, 畢竟它

看起來像指針。實際上,一些 C++ 編譯器對 NULL 的定義比較特殊,可以輸出有用的警告,特別是 sizeof(NULL)

就和 sizeof(0) 不一樣。

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

5.18. sizeof

Tip: 盡可能用 sizeof(varname) 代替 sizeof(type).

使用 sizeof(varname) 是因為當代碼中變量類型改變時會自動更新. 您或許會用 sizeof(type) 處理不涉及任

何變量的代碼,比如處理來自外部或內部的數據格式,這時用變量就不合適了。

Struct data;

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

Warning:

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

if (raw_size < sizeof(int)) {

LOG(ERROR) << "compressed record not big enough for count: " << raw_size;

return false;

}

5.19. auto

Tip: 用 auto 繞過煩瑣的類型名,只要可讀性好就繼續用,別用在局部變量之外的地方。

定義:

C++11 中,若變量被聲明成 auto, 那它的類型就會被自動匹配成初始化表達式的類型。您可以用 auto

來復制初始化或綁定引用。

vector<string> v;

...

auto s1 = v[0]; // 創建一份 v[0] 的拷貝。

const auto& s2 = v[0]; // s2 是 v[0] 的一個引用。

優點:

C++ 類型名有時又長又臭,特別是涉及模板或命名空間的時候。就像:

sparse_hash_map<string, int>::iterator iter = m.find(val);

返回類型好難讀,代碼目的也不夠一目了然。重構其:

auto iter = m.find(val);

好多了。

沒有 auto 的話,我們不得不在同一個表達式里寫同一個類型名兩次,無謂的重復,就像:

diagnostics::ErrorStatus* status = new diagnostics::ErrorStatus("xyz");

有了 auto, 可以更方便地用中間變量,顯式編寫它們的類型輕松點。

缺點:

類型夠明顯時,特別是初始化變量時,代碼才會夠一目了然。但以下就不一樣了:

auto i = x.Lookup(key);

看不出其類型是啥,x 的類型聲明恐怕遠在幾百行之外了。

程序員必須會區分 auto 和 const auto& 的不同之處,否則會復制錯東西。

auto 和 C++11 列表初始化的合體令人摸不著頭腦:

auto x(3); // 圓括號。

auto y{3}; // 大括號。

它們不是同一回事——x 是 int, y 則是 std::initializer_list<int>. 其它一般不可見的代理類型

(acgtyrant 注:normally-invisible proxy types, 它涉及到 C++ 鮮為人知的坑:Why is vector<bool> not

a STL container?)也有大同小異的陷阱。

如果在接口里用 auto, 比如聲明頭文件里的一個常量,那么只要僅僅因為程序員一時修改其值而導致類型

變化的話——API 要翻天覆地了。

結論:

auto 只能用在局部變量里用。別用在文件作用域變量,命名空間作用域變量和類數據成員里。永遠別列

表初始化 auto 變量。

auto 還可以和 C++11 特性「尾置返回類型(trailing return type)」一起用,不過后者只能用在 lambda

表達式里。

5.20. ?????

Tip: 你可以用列表初始化。

早在 C++03 里,聚合類型(aggregate types)就已經可以被列表初始化了,比如數組和不自帶構造函數的結構

體:

struct Point { int x; int y; };

Point p = {1, 2};

C++11 中,該特性得到進一步的推廣,任何對象類型都可以被列表初始化。示范如下:

// Vector 接收了一個初始化列表。

vector<string> v{"foo", "bar"};

// 不考慮細節上的微妙差別,大致上相同。

// 您可以任選其一。

vector<string> v = {"foo", "bar"};

// 可以配合 new 一起用。

auto p = new vector<string>{"foo", "bar"};

// map 接收了一些 pair, 列表初始化大顯神威。

map<int, string> m = {{1, "one"}, {2, "2"}};

// 初始化列表也可以用在返回類型上的隱式轉換。

vector<int> test_function() { return {1, 2, 3}; }

// 初始化列表可迭代。

for (int i : {-1, -2, -3}) {}

// 在函數調用里用列表初始化。

void TestFunction2(vector<int> v) {}

TestFunction2({1, 2, 3});

用戶自定義類型也可以定義接收 std::initializer_list<T> 的構造函數和賦值運算符,以自動列表初始化:

class MyType {

public:

// std::initializer_list 專門接收 init 列表。

// 得以值傳遞。

MyType(std::initializer_list<int> init_list) {

for (int i : init_list) append(i);

}

MyType& operator=(std::initializer_list<int> init_list) {

clear();

for (int i : init_list) append(i);

}

};

MyType m{2, 3, 5, 7};

最后,列表初始化也適用于常規數據類型的構造,哪怕沒有接收 std::initializer_list<T> 的構造函數。

double d{1.23};

// MyOtherType 沒有 std::initializer_list 構造函數,

// 直接上接收常規類型的構造函數。

class MyOtherType {

public:

explicit MyOtherType(string);

MyOtherType(int, string);

};

MyOtherType m = {1, "b"};

// 不過如果構造函數是顯式的(explict),您就不能用 `= {}` 了。

MyOtherType m{"b"};

千萬別直接列表初始化 auto 變量,看下一句,估計沒人看得懂:

Warning:

auto d = {1.23}; // d 即是 std::initializer_list<double>

auto d = double{1.23}; // 善哉 -- d 即為 double, 并非 std::initializer_list.

至于格式化,參見 braced-initializer-list-format.

5.21. Lambda ???

Tip: 適當使用 lambda 表達式。別用默認 lambda 捕獲,所有捕獲都要顯式寫出來。

定義:

Lambda 表達式是創建匿名函數對象的一種簡易途徑,常用于把函數當參數傳,例如:

std::sort(v.begin(), v.end(), [](int x, int y) {

return Weight(x) < Weight(y);

});

C++11 首次提出 Lambdas, 還提供了一系列處理函數對象的工具,比如多態包裝器(polymorphic

wrapper)std::function.

優點:

? 傳函數對象給 STL 算法,Lambdas 最簡易,可讀性也好。

? Lambdas, std::functions 和 std::bind 可以搭配成通用回調機制(general purpose callback mechanism);

寫接收有界函數為參數的函數也很容易了。

缺點:

? Lambdas 的變量捕獲略旁門左道,可能會造成懸空指針。

? Lambdas 可能會失控;層層嵌套的匿名函數難以閱讀。

結論:

? 按 format 小用 lambda 表達式怡情。

? 禁用默認捕獲,捕獲都要顯式寫出來。打比方,比起 [=](int x) {return x + n;}, 您該寫成 [n](int x)

{return x + n;} 才對,這樣讀者也好一眼看出 n 是被捕獲的值。

? 匿名函數始終要簡短,如果函數體超過了五行,那么還不如起名(acgtyrant 注:即把 lambda 表達式賦值給對

象),或改用函數。

? 如果可讀性更好,就顯式寫出 lambd 的尾置返回類型,就像 auto.

5.22. ??編程

Tip: 不要使用復雜的模板編程

定義:

模板編程指的是利用 c++ 模板實例化機制是圖靈完備性, 可以被用來實現編譯時刻的類型判斷的一系列

編程技巧

優點:

模板編程能夠實現非常靈活的類型安全的接口和極好的性能, 一些常見的工具比如 Google Test, std::tuple,

std::function 和 Boost.Spirit. 這些工具如果沒有模板是實現不了的

缺點:

? 模板編程所使用的技巧對于使用 c++ 不是很熟練的人是比較晦澀, 難懂的. 在復雜的地方使用模板的代碼讓人

更不容易讀懂, 并且 debug 和維護起來都很麻煩

? 模板編程經常會導致編譯出錯的信息非常不友好: 在代碼出錯的時候, 即使這個接口非常的簡單, 模板內部復雜

的實現細節也會在出錯信息顯示. 導致這個編譯出錯信息看起來非常難以理解.

? 大量的使用模板編程接口會讓重構工具 (Visual Assist X, Refactor for C++ 等等) 更難發揮用途. 首先模板的

代碼會在很多上下文里面擴展開來, 所以很難確認重構對所有的這些展開的代碼有用, 其次有些重構工具只對已

經做過模板類型替換的代碼的 AST 有用. 因此重構工具對這些模板實現的原始代碼并不有效, 很難找出哪些需

要重構.

結論:

? 模板編程有時候能夠實現更簡潔更易用的接口, 但是更多的時候卻適得其反. 因此模板編程最好只用在少量的基

礎組件, 基礎數據結構上, 因為模板帶來的額外的維護成本會被大量的使用給分擔掉

? 在使用模板編程或者其他復雜的模板技巧的時候, 你一定要再三考慮一下. 考慮一下你們團隊成員的平均水平是

否能夠讀懂并且能夠維護你寫的模板代碼. 或者一個非 c++ 程序員和一些只是在出錯的時候偶爾看一下代碼的

人能夠讀懂這些錯誤信息或者能夠跟蹤函數的調用流程. 如果你使用遞歸的模板實例化, 或者類型列表, 或者元

函數, 又或者表達式模板, 或者依賴 SFINAE, 或者 sizeof 的 trick 手段來檢查函數是否重載, 那么這說明你模板

用的太多了, 這些模板太復雜了, 我們不推薦使用

? 如果你使用模板編程, 你必須考慮盡可能的把復雜度最小化, 并且盡量不要讓模板對外暴漏. 你最好只在實現里

面使用模板, 然后給用戶暴露的接口里面并不使用模板, 這樣能提高你的接口的可讀性. 并且你應該在這些使用

模板的代碼上寫盡可能詳細的注釋. 你的注釋里面應該詳細的包含這些代碼是怎么用的, 這些模板生成出來的代

碼大概是什么樣子的. 還需要額外注意在用戶錯誤使用你的模板代碼的時候需要輸出更人性化的出錯信息. 因為

這些出錯信息也是你的接口的一部分, 所以你的代碼必須調整到這些錯誤信息在用戶看起來應該是非常容易理

解, 并且用戶很容易知道如何修改這些錯誤

5.23. Boost ?

Tip: 只使用 Boost 中被認可的庫.

定義:

Boost 庫集 是一個廣受歡迎, 經過同行鑒定, 免費開源的 C++ 庫集.

優點:

Boost 代碼質量普遍較高, 可移植性好, 填補了 C++ 標準庫很多空白, 如型別的特性, 更完善的綁定器, 更

好的智能指針。

缺點:

某些 Boost 庫提倡的編程實踐可讀性差, 比如元編程和其他高級模板技術, 以及過度 “函數化” 的編程風

格.

結論:

為了向閱讀和維護代碼的人員提供更好的可讀性, 我們只允許使用 Boost 一部分經認可的特性子集. 目前

允許使用以下庫:

? Call Traits : boost/call_traits.hpp

? Compressed Pair : boost/compressed_pair.hpp

? <The Boost Graph Library (BGL) : boost/graph, except serialization (adj_list_serialize.

hpp) and parallel/distributed algorithms and data structures(boost/graph/parallel/* and boost/

graph/distributed/*)

? Property Map : boost/property_map.hpp

? The part of Iterator that deals with defining iterators: boost/iterator/iterator_adaptor.hpp,

boost/iterator/iterator_facade.hpp, and boost/function_output_iterator.hpp

? The part of Polygon that deals with Voronoi diagram construction and doesn’t depend on the rest

of Polygon: boost/polygon/voronoi_builder.hpp, boost/polygon/voronoi_diagram.hpp, and

boost/polygon/voronoi_geometry_type.hpp

? Bimap : boost/bimap

? Statistical Distributions and Functions : boost/math/distributions

? Multi-index : boost/multi_index

? Heap : boost/heap

? The flat containers from Container: boost/container/flat_map, and boost/container/flat_set

我們正在積極考慮增加其它 Boost 特性, 所以列表中的規則將不斷變化.

以下庫可以用,但由于如今已經被 C++ 11 標準庫取代,不再鼓勵:

? Pointer Container : boost/ptr_container, 改用 std::unique_ptr

? Array : boost/array.hpp, 改用 std::array

5.24. C++11

Tip: 適當用 C++11(前身是 C++0x)的庫和語言擴展,在貴項目用 C++11 特性前三思可移植性。

定義:

C++11 有眾多語言和庫上的 ‘變革 <https://en.wikipedia.org/wiki/C%2B%2B11>‘_ 。

優點:

在二〇一四年八月之前,C++11 一度是官方標準,被大多 C++ 編譯器支持。它標準化很多我們早先就

在用的 C++ 擴展,簡化了不少操作,大大改善了性能和安全。

缺點:

C++11 相對于前身,復雜極了:1300 頁 vs 800 頁!很多開發者也不怎么熟悉它。于是從長遠來看,前

者特性對代碼可讀性以及維護代價難以預估。我們說不準什么時候采納其特性,特別是在被迫依賴老實工

具的項目上。

和5.23. Boost 庫 一樣,有些 C++11 擴展提倡實則對可讀性有害的編程實踐——就像去除冗余檢查(比

如類型名)以幫助讀者,或是鼓勵模板元編程等等。有些擴展在功能上與原有機制沖突,容易招致困惑以

及遷移代價。

缺點:

C++11 特性除了個別情況下,可以用一用。除了本指南會有不少章節會加以討若干 C++11 特性之外,

以下特性最好不要用:

? 尾置返回類型,比如用 auto foo() -> int 代替 int foo(). 為了兼容于現有代碼的聲明風格。

? 編譯時合數 <ratio>, 因為它涉及一個重模板的接口風格。

? <cfenv> 和 <fenv.h> 頭文件,因為編譯器尚不支持。

? 默認 lambda 捕獲。

??δ

acgtyrantε??

1. 實際上,缺省參數會改變函數簽名的前提是改變了它接收的參數數量,比如把 void a() 改成 void a(int b =

0), 開發者改變其代碼的初衷也許是,在不改變「代碼兼容性」的同時,又提供了可選 int 參數的余地,然而這

終究會破壞函數指針上的兼容性,畢竟函數簽名確實變了。

2. 此外把自帶缺省參數的函數地址賦值給指針時,會丟失缺省參數信息。

3. 我還發現 濫用缺省參數會害得讀者光只看調用代碼的話,會誤以為其函數接受的參數數量比實際上還要少。

4. friend 實際上只對函數/類賦予了對其所在類的訪問權限,并不是有效的聲明語句。所以除了在頭文件類內部

寫 friend 函數/類,還要在類作用域之外正式地聲明一遍,最后在對應的 .cc 文件加以定義。

5. 本風格指南都強調了「友元應該定義在同一文件內,避免代碼讀者跑到其它文件查找使用該私有成員的類」。那

么可以把其聲明放在類聲明所在的頭文件,定義也放在類定義所在的文件。

6. 由于友元函數/類并不是類的一部分,自然也不會是類可調用的公有接口,于是我主張全集中放在類的尾部,

即的數據成員之后,參考聲明順序 。

7. 對使用 C++ 異常處理應具有怎樣的態度? 非常值得一讀。

8. 注意初始化 const 對象時,必須在初始化的同時值初始化。

9. 用斷言代替無符號整型類型,深有啟發。

10. auto 在涉及迭代器的循環語句里挺常用。

11. Should the trailing return type syntax style become the default for new C++11 programs? 討論了 auto 與尾置

返回類型一起用的全新編碼風格,值得一看。

6. ????

最重要的一致性規則是命名管理. 命名風格快速獲知名字代表是什么東東: 類型? 變量? 函數? 常量? 宏... ? 甚

至不需要去查找類型聲明. 我們大腦中的模式匹配引擎可以非常可靠的處理這些命名規則.

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

6.1. ??????

Tip: 函數命名,變量命名,文件命名要有描述性;少用縮寫。

盡可能給有描述性的命名,別心疼空間,畢竟讓代碼易于新讀者理解很重要。不要用只有項目開發者能理解的縮

寫,也不要通過砍掉幾個字母來縮寫單詞。

int price_count_reader; // 無縮寫

int num_errors; // “num” 本來就很常見

int num_dns_connections; // 人人都知道 “DNS” 是啥

Warning:

int n; // 莫名其妙。

int nerr; // 怪縮寫。

int n_comp_conns; // 怪縮寫。

int wgc_connections; // 只有貴團隊知道是啥意思。

int pc_reader; // "pc" 有太多可能的解釋了。

int cstmr_id; // 有刪減若干字母。

6.2. ????

Tip: 文件名要全部小寫, 可以包含下劃線 (_) 或連字符 (-). 按項目約定來. 如果并沒有項目約定,”_” 更好。

可接受的文件命名:

* my_useful_class.cc

* my-useful-class.cc

* myusefulclass.cc

* muusefulclass_test.cc // ``_unittest`` 和 ``_regtest`` 已棄用。

C++ 文件要以 .cc 結尾, 頭文件以 .h 結尾. 專門插入文本的文件則以 .inc 結尾,參見1.1. Self-contained 頭

文件。

不要使用已經存在于 /usr/include 下的文件名 (Yang.Y 注: 即編譯器搜索系統頭文件的路徑), 如 db.h.

通常應盡量讓文件名更加明確. http_server_logs.h 就比 logs.h 要好. 定義類時文件名一般成對出現, 如

foo_bar.h 和 foo_bar.cc, 對應于類 FooBar.

內聯函數必須放在 .h 文件中. 如果內聯函數比較短, 就直接放在 .h 中.

6.3. ????

Tip: 類型名稱的每個單詞首字母均大寫, 不包含下劃線: MyExcitingClass, MyExcitingEnum.

所有類型命名——類, 結構體, 類型定義 (typedef), 枚舉——均使用相同約定. 例如:

// classes and structs

class UrlTable { ...

class UrlTableTester { ...

struct UrlTableProperties { ...

// typedefs

typedef hash_map<UrlTableProperties *, string> PropertiesMap;

// enums

enum UrlTableErrors { ...

6.4. ????

Tip: 變量名一律小寫, 單詞之間用下劃線連接. 類的成員變量以下劃線結尾, 但結構體的就不用,如::

a_local_variable, a_struct_data_member, a_class_data_member_.

普通變量命名:

舉例:

string table_name; // 可 - 用下劃線。

string tablename; // 可 - 全小寫。

Warning:

string tableName; // 差 - 混合大小寫。

類數據成員:

不管是靜態的還是非靜態的,類數據成員都可以和普通變量一樣, 但要接下劃線。

class TableInfo {

...

private:

string table_name_; // 可 - 尾后加下劃線。

string tablename_; // 可。

static Pool<TableInfo>* pool_; // 可。

};

結構體變量:

不管是靜態的還是非靜態的,結構體數據成員都可以和普通變量一樣, 不用像類那樣接下劃線:

struct UrlTableProperties {

string name;

int num_entries;

}

結構體與類的討論參考結構體 vs. 類 一節.

全局變量:

對全局變量沒有特別要求, 少用就好, 但如果你要用, 可以用 g_ 或其它標志作為前綴, 以便更好的區分局

部變量.

6.5. ????

Tip: 在全局或類里的常量名稱前加 k: kDaysInAWeek. 且除去開頭的 k 之外每個單詞開頭字母均大寫。

所有編譯時常量, 無論是局部的, 全局的還是類中的, 和其他變量稍微區別一下. k 后接大寫字母開頭的單詞:

const int kDaysInAWeek = 7;

這規則適用于編譯時的局部作用域常量,不過要按變量規則來命名也可以。

6.6. ????

Tip: 常 規 函 數 使 用 大 小 寫 混 合, 取 值 和 設 值 函 數 則 要 求 與 變 量 名 匹 配: MyExcitingFunction(),

MyExcitingMethod(), my_exciting_member_variable(), set_my_exciting_member_variable().

常規函數:

函數名的每個單詞首字母大寫, 沒有下劃線。

如果您的某函數出錯時就要直接 crash, 那么就在函數名加上 OrDie. 但這函數本身必須集成在產品代碼

里,且平時也可能會出錯。

AddTableEntry()

DeleteUrl()

OpenFileOrDie()

取值和設值函數:

取值(Accessors)和設值(Mutators)函數要與存取的變量名匹配. 這兒摘錄一個類, num_entries_ 是該

類的實例變量:

class MyClass {

public:

...

int num_entries() const { return num_entries_; }

void set_num_entries(int num_entries) { num_entries_ = num_entries; }

private:

int num_entries_;

};

其它非常短小的內聯函數名也可以用小寫字母, 例如. 如果你在循環中調用這樣的函數甚至都不用緩存其

返回值, 小寫命名就可以接受.

6.7. ??グ???

Tip: 名字空間用小寫字母命名, 并基于項目名稱和目錄結構: google_awesome_project.

關于名字空間的討論和如何命名, 參考名字空間 一節.

6.8. ????

Tip: 枚舉的命名應當和常量 或宏 一致: kEnumName 或是 ENUM_NAME.

????????????

??

?????

. ? ? ?????????? . ??? UrlTableErrors (?? AlternateUrlTableErrors) ??? , ???????????? .

enum UrlTableErrors {

kOK = 0,

kErrorOutOfMemory,

kErrorMalformedInput,

};

enum AlternateUrlTableErrors {

OK = 0,

OUT_OF_MEMORY = 1,

MALFORMED_INPUT = 2,

};

2009 年 1 月之前, 我們一直建議采用宏 的方式命名枚舉值. 由于枚舉值和宏之間的命名沖突, 直接導致了很多問

題. 由此, 這里改為優先選擇常量風格的命名方式. 新代碼應該盡可能優先使用常量風格. 但是老代碼沒必要切換到常

量風格, 除非宏風格確實會產生編譯期問題.

6.9. ???

Tip: 你并不打算使用宏, 對吧? 如果你一定要用, 像這樣命名: MY_MACRO_THAT_SCARES_SMALL_CHILDREN.

參考預處理宏; 通常 不應該 使用宏. 如果不得不用, 其命名像枚舉命名一樣全部大寫, 使用下劃線:

#define ROUND(x) ...

#define PI_ROUNDED 3.0

6.10. ???????

Tip: 如果你命名的實體與已有 C/C++ 實體相似, 可參考現有命名策略.

bigopen():

函數名, 參照 open() 的形式

uint:

typedef

bigpos:

struct 或 class, 參照 pos 的形式

sparse_hash_map:

STL 相似實體; 參照 STL 命名約定

LONGLONG_MAX:

常量, 如同 INT_MAX

??δ

acgtyrantε??

1. 感 覺 Google 的 命 名 約 定 很 高 明,比 如 寫 了 簡 單 的 類 QueryResult, 接 著 又 可 以 直 接 定 義 一 個 變 量

query_result, 區分度很好;再次,類內變量以下劃線結尾,那么就可以直接傳入同名的形參,比如

TextQuery::TextQuery(std::string word) : word_(word) {} , 其中 word_ 自然是類內私有成員。

7. ??

注釋雖然寫起來很痛苦, 但對保證代碼可讀性至關重要. 下面的規則描述了如何注釋以及在哪兒注釋. 當然也要

記住: 注釋固然很重要, 但最好的代碼本身應該是自文檔化. 有意義的類型名和變量名, 要遠勝過要用注釋解釋的含糊

不清的名字.

你寫的注釋是給代碼讀者看的: 下一個需要理解你的代碼的人. 慷慨些吧, 下一個人可能就是你!

7.1. ??風格

Tip: 使用 // 或 /* */, 統一就好.

// 或 /* */ 都可以; 但 // 更 常用. 要在如何注釋及注釋風格上確保統一.

7.2. ????

Tip: 在每一個文件開頭加入版權公告, 然后是文件內容描述.

法律公告和作者信息:

每個文件都應該包含以下項, 依次是:

? 版權聲明 (比如, Copyright 2008 Google Inc.)

? 許可證. 為項目選擇合適的許可證版本 (比如, Apache 2.0, BSD, LGPL, GPL)

? 作者: 標識文件的原始作者.

如果你對原始作者的文件做了重大修改, 將你的信息添加到作者信息里. 這樣當其他人對該文件有疑問時

可以知道該聯系誰.

文件內容:

緊接著版權許可和作者信息之后, 每個文件都要用注釋描述文件內容.

通常, .h 文件要對所聲明的類的功能和用法作簡單說明. .cc 文件通常包含了更多的實現細節或算法技巧

討論, 如果你感覺這些實現細節或算法技巧討論對于理解 .h 文件有幫助, 可以將該注釋挪到 .h, 并在 .cc

中指出文檔在 .h.

不要簡單的在 .h 和 .cc 間復制注釋. 這種偏離了注釋的實際意義.

7.3. ???

Tip: 每個類的定義都要附帶一份注釋, 描述類的功能和用法.

// 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 {

...

};

如果你覺得已經在文件頂部詳細描述了該類, 想直接簡單的來上一句 “完整描述見文件頂部” 也不打緊, 但務必確

保有這類注釋.

如果類有任何同步前提, 文檔說明之. 如果該類的實例可被多線程訪問, 要特別注意文檔說明多線程環境下相關的

規則和常量使用.

7.4. ????

Tip: 函數聲明處注釋描述函數功能; 定義處描述函數實現.

函數聲明:

注釋位于聲明之前, 對函數功能及用法進行描述. 注釋使用敘述式 (“Opens the file”) 而非指令式 (“Open

the file”); 注釋只是為了描述函數, 而不是命令函數做什么. 通常, 注釋不會描述函數如何工作. 那是函數

定義部分的事情.

函數聲明處注釋的內容:

? 函數的輸入輸出.

? 對類成員函數而言: 函數調用期間對象是否需要保持引用參數, 是否會釋放這些參數.

? 如果函數分配了空間, 需要由調用者釋放.

? 參數是否可以為 NULL.

? 是否存在函數使用上的性能隱患.

? 如果函數是可重入的, 其同步前提是什么?

舉例如下:

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

// responsibility to delete the iterator when it is done with it,

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

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

//

// The iterator is initially positioned at the beginning of the table.

//

// This method is equivalent to:

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

// iter->Seek("");

// return iter;

// If you are going to immediately seek to another place in the

// returned iterator, it will be faster to use NewIterator()

// and avoid the extra seek.

Iterator* GetIterator() const;

但也要避免羅羅嗦嗦, 或做些顯而易見的說明. 下面的注釋就沒有必要加上 “returns false otherwise”, 因為

已經暗含其中了:

// Returns true if the table cannot hold any more entries.

bool IsTableFull();

注釋構造/析構函數時, 切記讀代碼的人知道構造/析構函數是干啥的, 所以 “destroys this object” 這樣的

注釋是沒有意義的. 注明構造函數對參數做了什么 (例如, 是否取得指針所有權) 以及析構函數清理了什么.

如果都是些無關緊要的內容, 直接省掉注釋. 析構函數前沒有注釋是很正常的.

函數定義:

每個函數定義時要用注釋說明函數功能和實現要點. 比如說說你用的編程技巧, 實現的大致步驟, 或解釋如

此實現的理由, 為什么前半部分要加鎖而后半部分不需要.

不要 從 .h 文件或其他地方的函數聲明處直接復制注釋. 簡要重述函數功能是可以的, 但注釋重點要放在

如何實現上.

7.5. ????

Tip: 通常變量名本身足以很好說明變量用途. 某些情況下, 也需要額外的注釋說明.

類數據成員:

每個類數據成員 (也叫實例變量或成員變量) 都應該用注釋說明用途. 如果變量可以接受 NULL 或 -1 等警

戒值, 須加以說明. 比如:

private:

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

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

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

int num_total_entries_;

全局變量:

和數據成員一樣, 所有全局變量也要注釋說明含義及用途. 比如:

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

const int kNumTestCases = 6;

7.6. ????

Tip: 對于代碼中巧妙的, 晦澀的, 有趣的, 重要的地方加以注釋.

代碼前注釋:

巧妙或復雜的代碼段前要加注釋. 比如:

// 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)[i];

(*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.

注意, 這里用了兩段注釋分別描述這段代碼的作用, 和提示函數返回時錯誤已經被記入日志.

如果你需要連續進行多行注釋, 可以使之對齊獲得更好的可讀性:

DoSomething(); // Comment here so the comments line up.

DoSomethingElseThatIsLonger(); // Comment here so there are two spaces between

// the code and the comment.

{ // One space before comment when opening a new scope is allowed,

// thus the comment lines up with the following comments and code.

DoSomethingElse(); // Two spaces before line comments normally.

}

NULL, true/false, 1, 2, 3...:

向函數傳入 NULL, 布爾值或整數時, 要注釋說明含義, 或使用常量讓代碼望文知意. 例如, 對比:

Warning:

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);

不允許:

注意 永遠不要 用自然語言翻譯代碼作為注釋. 要假設讀代碼的人 C++ 水平比你高, 即便他/她可能不知

道你的用意:

Warning:

// 現在, 檢查 b 數組并確保 i 是否存在,

// 下一個元素是 i+1.

... // 天哪. 令人崩潰的注釋.

7.7. ?? , ?????

Tip: 注意標點, 拼寫和語法; 寫的好的注釋比差的要易讀的多.

注釋的通常寫法是包含正確大小寫和結尾句號的完整語句. 短一點的注釋 (如代碼行尾注釋) 可以隨意點, 依然要

注意風格的一致性. 完整的語句可讀性更好, 也可以說明該注釋是完整的, 而不是一些不成熟的想法.

雖然被別人指出該用分號時卻用了逗號多少有些尷尬, 但清晰易讀的代碼還是很重要的. 正確的標點, 拼寫和語法

對此會有所幫助.

7.8. TODO ??

Tip: 對那些臨時的, 短期的解決方案, 或已經夠好但仍不完美的代碼使用 TODO 注釋.

TODO 注釋要使用全大寫的字符串 TODO, 在隨后的圓括號里寫上你的大名, 郵件地址, 或其它身份標識. 冒號是可

選的. 主要目的是讓添加注釋的人 (也是可以請求提供更多細節的人) 可根據規范的 TODO 格式進行查找. 添加 TODO

注釋并不意味著你要自己來修正.

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

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

如果加 TODO 是為了在 “將來某一天做某事”, 可以附上一個非常明確的時間 “Fix by November 2005”), 或者一個

明確的事項 (“Remove this code when all clients can handle XML responses.”).

7.9. ????

Tip: 通過棄用注釋(DEPRECATED comments)以標記某接口點(interface points)已棄用。

您可以寫上包含全大寫的 DEPRECATED 的注釋,以標記某接口為棄用狀態。注釋可以放在接口聲明前,或者同一

行。

在 DEPRECATED 一詞后,留下您的名字,郵箱地址以及括號補充。

僅僅標記接口為 DEPRECATED 并不會讓大家不約而同地棄用,您還得親自主動修正調用點(callsites),或是找個

幫手。

修正好的代碼應該不會再涉及棄用接口點了,著實改用新接口點。如果您不知從何下手,可以找標記棄用注釋的

當事人一起商量。

??

(YuleFox) ??

1. 關于注釋風格,很多 C++ 的 coders 更喜歡行注釋, C coders 或許對塊注釋依然情有獨鐘, 或者在文件頭大段

大段的注釋時使用塊注釋;

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

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

4. 對于 Chinese coders 來說, 用英文注釋還是用中文注釋, it is a problem, 但不管怎樣, 注釋是為了讓別人看懂, 難

道是為了炫耀編程語言之外的你的母語或外語水平嗎;

5. 注釋不要太亂, 適當的縮進才會讓人樂意看. 但也沒有必要規定注釋從第幾列開始 (我自己寫代碼的時候總喜歡

這樣), UNIX/LINUX 下還可以約定是使用 tab 還是 space, 個人傾向于 space;

6. TODO 很不錯, 有時候, 注釋確實是為了標記一些未完成的或完成的不盡如人意的地方, 這樣一搜索, 就知道還

有哪些活要干, 日志都省了.

8. 格?

代碼風格和格式確實比較隨意, 但一個項目中所有人遵循同一風格是非常容易的. 個體未必同意下述每一處格式

規則, 但整個項目服從統一的編程風格是很重要的, 只有這樣才能讓所有人能很輕松的閱讀和理解代碼.

另外, 我們寫了一個 emacs 配置文件 來幫助你正確的格式化代碼.

8.1. ???

Tip: 每一行代碼字符數不超過 80.

我們也認識到這條規則是有爭議的, 但很多已有代碼都已經遵照這一規則, 我們感覺一致性更重要.

優點:

提倡該原則的人主張強迫他們調整編輯器窗口大小很野蠻. 很多人同時并排開幾個代碼窗口, 根本沒有多

余空間拉伸窗口. 大家都把窗口最大尺寸加以限定, 并且 80 列寬是傳統標準. 為什么要改變呢?

缺點:

反對該原則的人則認為更寬的代碼行更易閱讀. 80 列的限制是上個世紀 60 年代的大型機的古板缺陷; 現

代設備具有更寬的顯示屏, 很輕松的可以顯示更多代碼.

結論:

80 個字符是最大值.

特例:

? 如果一行注釋包含了超過 80 字符的命令或 URL, 出于復制粘貼的方便允許該行超過 80 字符.

? 包含長路徑的 #include 語句可以超出 80 列. 但應該盡量避免.

? 頭文件保護 可以無視該原則.

8.2. ? ASCII ??

Tip: 盡量不使用非 ASCII 字符, 使用時必須使用 UTF-8 編碼.

即使是英文, 也不應將用戶界面的文本硬編碼到源代碼中, 因此非 ASCII 字符要少用. 特殊情況下可以適當包含

此類字符. 如, 代碼分析外部數據文件時, 可以適當硬編碼數據文件中作為分隔符的非 ASCII 字符串; 更常見的是 (不

需要本地化的) 單元測試代碼可能包含非 ASCII 字符串. 此類情況下, 應使用 UTF-8 編碼, 因為很多工具都可以理解

和處理 UTF-8 編碼.

十六進制編碼也可以, 能增強可讀性的情況下尤其鼓勵——比如 "\xEF\xBB\xBF" 在 Unicode 中是 零寬度無間斷

的間隔符號, 如果不用十六進制直接放在 UTF-8 格式的源文件中, 是看不到的.

(Yang.Y 注: "\xEF\xBB\xBF" 通常用作 UTF-8 with BOM 編碼標記)

用 u8 前綴以把帶 uXXXX 轉義序列的字符串字面值編碼成 UTF-8. 不要用在本身就帶 UTF-8 字符的字符串字面

值上,因為如果編譯器不把源代碼識別成 UTF-8, 輸出就會出錯。

別用 C++11 的 char16_t 和 char32_t, 它們和 UTF-8 文本沒有關系,wchar_t 同理,除非您寫的代碼要調用

Windows API, 后者有用到 wchar_t 擴展。

8.3. グ格?????

Tip: 只使用空格, 每次縮進 2 個空格.

我們使用空格縮進. 不要在代碼中使用制符表. 你應該設置編輯器將制符表轉為空格.

8.4. ????ф??

Tip: 返回類型和函數名在同一行, 參數也盡量放在同一行,如果放不下就對形參分行。

函數看上去像這樣:

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

DoSomething();

...

}

如果同一行文本太多, 放不下所有參數:

ReturnType ClassName::ReallyLongFunctionName(Type par_name1, Type par_name2,

Type par_name3) {

DoSomething();

...

}

甚至連第一個參數都放不下:

ReturnType LongClassName::ReallyReallyReallyLongFunctionName(

Type par_name1, // 4 空格縮進

Type par_name2,

Type par_name3) {

DoSomething(); // 2 空格縮進

...

}

注意以下幾點:

? 如果返回類型和函數名在一行放不下,分行。

? 如果返回類型那個與函數聲明或定義分行了,不要縮進。

? 左圓括號總是和函數名在同一行;

? 函數名和左圓括號間沒有空格;

? 圓括號與參數間沒有空格;

? 左大括號總在最后一個參數同一行的末尾處;

? 如果其它風格規則允許的話,右大括號總是單獨位于函數最后一行,或者與左大括號同一行。

? 右大括號和左大括號間總是有一個空格;

? 函數聲明和定義中的所有形參必須有命名且一致;

? 所有形參應盡可能對齊;

? 缺省縮進為 2 個空格;

? 換行后的參數保持 4 個空格的縮進;

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

// 接口中形參恒有命名。

class Shape {

public:

virtual void Rotate(double radians) = 0;

}

// 聲明中形參恒有命名。

class Circle : public Shape {

public:

virtual void Rotate(double radians);

}

// 定義中注釋掉無用變量。

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

Warning:

// 差 - 如果將來有人要實現,很難猜出變量是干什么用的。

void Circle::Rotate(double) {}

8.5. Lambda ???

Tip: 其它函數怎么格式化形參和函數體,Lambda 表達式就怎么格式化;捕獲列表同理。

若用引用捕獲,在變量名和 & 之間不留空格。

int x = 0;

auto add_to_x = [&x](int n) { x += n; };

短 lambda 就寫得和內聯函數一樣。

std::set<int> blacklist = {7, 8, 9};

std::vector<int> digits = {3, 9, 1, 8, 4, 7, 1};

digits.erase(std::remove_if(digits.begin(), digits.end(), [&blacklist](int i) {

return blacklist.find(i) != blacklist.end();

}),

digits.end());

8.6. ????

Tip: 要么一行寫完函數調用,要么在圓括號里對參數分行,要么參數另起一行且縮進四格。如果沒有其它顧慮的

話,盡可能精簡行數,比如把多個參數適當地放在同一行里。

函數調用遵循如下形式:

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

如果同一行放不下,可斷為多行,后面每一行都和第一個實參對齊,左圓括號后和右圓括號前不要留空格:

bool retval = DoSomething(averyveryveryverylongargument1,

argument2, argument3);

參數也可以放在次行,縮進四格:

if (...) {

...

...

if (...) {

DoSomething(

argument1, argument2, // 4 空格縮進

argument3, argument4);

}

把多個參數放在同一行,是為了減少函數調用所需的行數,除非影響到可讀性。有人認為把每個參數都獨立成

行,不僅更好讀,而且方便編輯參數。不過,比起所謂的參數編輯,我們更看重可讀性,且后者比較好辦:

如果一些參數本身就是略復雜的表達式,且降低了可讀性。那么可以直接創建臨時變量描述該表達式,并傳遞給

函數:

int my_heuristic = scores[x] * y + bases[x];

bool retval = DoSomething(my_heuristic, x, y, z);

或者放著不管,補充上注釋:

bool retval = DoSomething(scores[x] * y + bases[x], // Score heuristic.

x, y, z);

如果某參數獨立成行,對可讀性更有幫助的話,就這么辦。

此外,如果一系列參數本身就有一定的結構,可以酌情地按其結構來決定參數格式:

// 通過 3x3 矩陣轉換 widget.

my_widget.Transform(x1, x2, x3,

y1, y2, y3,

z1, z2, z3);

8.7. ?????格?

Tip: 您平時怎么格式化函數調用,就怎么格式化5.20. 列表初始化。

如果列表初始化伴隨著名字,比如類型或變量名,您可以當名字是函數、{} 是函數調用的括號來格式化它。反

之,就當它有個長度為零的名字。

// 一行列表初始化示范。

return {foo, bar};

functioncall({foo, bar});

pair<int, int> p{foo, bar};

// 當不得不斷行時。

SomeFunction(

{"assume a zero-length name before {"},

some_other_function_parameter);

SomeType variable{

some, other, values,

{"assume a zero-length name before {"},

SomeOtherType{

"Very long string requiring the surrounding breaks.",

some, other values},

SomeOtherType{"Slightly shorter string",

some, other, values}};

SomeType variable{

"This is too long to fit all in one line"};

MyType m = { // 注意了,您可以在 { 前斷行。

superlongvariablename1,

superlongvariablename2,

{short, interior, list},

{interiorwrappinglist,

interiorwrappinglist2}};

8.8. ????

Tip: 傾向于不在圓括號內使用空格. 關鍵字 if 和 else 另起一行.

對基本條件語句有兩種可以接受的格式. 一種在圓括號和條件之間有空格, 另一種沒有.

最常見的是沒有空格的格式. 哪種都可以, 但 保持一致性. 如果你是在修改一個文件, 參考當前已有格式. 如果是

寫新的代碼, 參考目錄下或項目中其它文件. 還在徘徊的話, 就不要加空格了.

if (condition) { 圓括號里沒空格緊鄰。

... // 2 空格縮進。

} else { // else 與 if 的右括號同一行。

...

}

如果你更喜歡在圓括號內部加空格:

if ( condition ) { // 圓括號與空格緊鄰 - 不常見

... // 2 空格縮進。

} else { // else 與 if 的右括號同一行。

...

}

注意所有情況下 if 和左圓括號間都有個空格. 右圓括號和左大括號之間也要有個空格:

Warning:

if(condition) // 差 - IF 后面沒空格。

if (condition){ // 差 - { 前面沒空格。

if(condition){ // 變本加厲地差。

if (condition) { // 可 - IF 和 { 都與空格緊鄰。

如果能增強可讀性, 簡短的條件語句允許寫在同一行. 只有當語句簡單并且沒有使用 else 子句時使用:

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

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

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

Warning:

// 不可以這樣子 - 當有 ELSE 分支時 IF 塊卻只有一行

if (x) DoThis();

else DoThat();

通常, 單行語句不需要使用大括號, 如果你喜歡用也沒問題; 復雜的條件或循環語句用大括號可讀性會更好. 也有

一些項目要求 if 必須總是使用大括號:

if (condition)

DoSomething(); // 2 空格縮進。

if (condition) {

DoSomething(); // 2 空格縮進。

}

但如果語句中某個 if-else 分支使用了大括號的話, 其它分支也必須使用:

Warning:

// 不可以這樣子 - IF 有大括號 ELSE 卻沒有。

if (condition) {

foo;

} else

bar;

// 不可以這樣子 - ELSE 有大括號 IF 卻沒有。

if (condition)

foo;

else {

bar;

}

// 只要其中一個分支用了大括號,兩個分支都要用上大括號。

if (condition) {

foo;

} else {

bar;

}

8.9. ?????????

Tip: switch 語句可以使用大括號分段,以表明 cases 之間不是連在一起的。在單語句循環里,括號可用可不用。

空循環體應使用 {} 或 continue.

switch 語句中的 case 塊可以使用大括號也可以不用, 取決于你的個人喜好. 如果用的話, 要按照下文所述的方

法.

如果有不滿足 case 條件的枚舉值, switch 應該總是包含一個 default 匹配 (如果有輸入值沒有 case 去處理, 編

譯器將報警). 如果 default 應該永遠執行不到, 簡單的加條 assert:

switch (var) {

case 0: { // 2 空格縮進

... // 4 空格縮進

break;

}

case 1: {

...

break;

}

default: {

assert(false);

}

}

在單語句循環里,括號可用可不用:

for (int i = 0; i < kSomeNumber; ++i)

printf("I love you\n");

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

printf("I take it back\n");

}

空循環體應使用 {} 或 continue, 而不是一個簡單的分號.

while (condition) {

// 反復循環直到條件失效。

}

for (int i = 0; i < kSomeNumber; ++i) {} // 可 - 空循環體。

while (condition) continue; // 可 - contunue 表明沒有邏輯。

Warning:

while (condition); // 差 - 看起來僅僅只是 while/loop 的部分之一。

8.10. 指???????

Tip: 句點或箭頭前后不要有空格. 指針/地址操作符 (*, &) 之后不能有空格.

下面是指針和引用表達式的正確使用范例:

x = *p;

p = &x;

x = r.y;

x = r->y;

??

: ?

? 在訪問成員時, 句點或箭頭前后沒有空格.

? 指針操作符 * 或 & 后沒有空格.

在聲明指針變量或參數時, 星號與類型或變量名緊挨都可以:

// 好樣的,空格前置。

char *c;

const string &str;

// 好樣的,空格后置。

char* c; // 但別忘了 "char* c, *d, *e, ...;"!

const string& str;

Warning:

char * c; // 差 - * 兩邊都有空格

const string & str; // 差 - & 兩邊都有空格。

在單個文件內要保持風格一致, 所以, 如果是修改現有文件, 要遵照該文件的風格.

8.11. ?????

Tip: 如果一個布爾表達式超過標準行寬, 斷行方式要統一一下.

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

if (this_one_thing > this_other_thing &&

a_third_thing == a_fourth_thing &&

yet_another & last_one) {

...

}

注意, 上例的邏輯與 (&&) 操作符均位于行尾. 這格式在 Google 里很常見,您要把所有操作符放在開頭也可以。

可以考慮額外插入圓括號, 合理使用的話對增強可讀性是很有幫助的. 此外直接用符號形式的操作符,比如 && 和 ~,

不要用詞語形式的 and 和 compl.

8.12. ?????

Tip: return 表達式里時沒必要都用圓括號。

假如您寫 x = epr 時本來就會加上括號,那 return expr; 也可如法炮制。

函數返回時不要使用圓括號:

return result; // 返回值很簡單,沒有圓括號。

// 可以用圓括號把復雜表達式圈起來,改善可讀性。

return (some_long_condition &&

another_condition);

Warning:

return (value); // 畢竟您從來不會寫 var = (value);

return(result); // return 可不是函數!

8.13. ????????

Tip: 用 =, () 和 {} 均可.

您可以用 =, () 和 {}, 以下都對:

int x = 3;

int x(3);

int x{3};

string name("Some Name");

string name = "Some Name";

string name{"Some Name"};

請務必小心列表初始化 {...} 用 std::initializer_list 構造函數初始化出的類型。非空列表初始化就會

優先調用 std::initializer_list, 不過空列表初始化除外,后者原則上會調用默認構造函數。為了強制禁用

std::initializer_list 構造函數,請改用括號。

vector<int> v(100, 1); // A vector of 100 1s.

vector<int> v{100, 1}; // A vector of 100, 1.

此外,列表初始化不允許整型類型的四舍五入,這可以用來避免一些類型上的編程失誤。

int pi(3.14); // 可 -- pi == 3.

int pi{3.14}; // Compile error: narrowing conversion.

8.14. 人??指?

Tip: 預處理指令不要縮進, 從行首開始.

即使預處理指令位于縮進代碼塊中, 指令也應從行首開始.

// 可 - directives at beginning of line

if (lopsided_score) {

#if DISASTER_PENDING // 正確 -- 行開頭起。

DropEverything();

#endif

BackToNormal();

}

Warning:

// 差 - indented directives

if (lopsided_score) {

#if DISASTER_PENDING // 錯了! "#if" 應該放在行開頭

DropEverything();

#endif // 錯了! "#endif" 不要縮進

BackToNormal();

}

8.15. ?格?

Tip: 訪問控制塊的聲明依次序是 public:, protected:, private:, 每次縮進 1 個空格.

類聲明 (對類注釋不了解的話, 參考類注釋) 的基本格式如下:

class MyClass : public OtherClass {

public: // 注意有 1 空格縮進!

MyClass(); // 照常,2 空格縮進。

explicit MyClass(int var);

~MyClass() {}

void SomeFunction();

void SomeFunctionThatDoesNothing() {

}

void set_some_var(int var) { some_var_ = var; }

int some_var() const { return some_var_; }

private:

bool SomeInternalFunction();

int some_var_;

int some_other_var_;

DISALLOW_COPY_AND_ASSIGN(MyClass);

};

注意事項:

? 所有基類名應在 80 列限制下盡量與子類名放在同一行.

? 關鍵詞 public:, protected:, private: 要縮進 1 個空格.

? 除第一個關鍵詞 (一般是 public) 外, 其他關鍵詞前要空一行. 如果類比較小的話也可以不空.

? 這些關鍵詞后不要保留空行.

? public 放在最前面, 然后是 protected, 最后是 private.

? 關于聲明順序的規則請參考聲明順序 一節.

8.16. ?????????

Tip: 構造函數初始值列表放在同一行或按四格縮進并排幾行.

下面兩種初始值列表方式都可以接受:

// 當全放在一行合適時:

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

// 如果要斷成多行,縮進四格,冒號放在第一行初始化句:

MyClass::MyClass(int var)

: some_var_(var), // 4 空格縮進

some_other_var_(var + 1) { // 對準

...

DoSomething();

...

}

8.17. ??グ?格??

Tip: 名字空間內容不縮進.

名字空間 不要增加額外的縮進層次, 例如:

namespace {

void foo() { // 正確。命名空間內沒有額外的縮進。

...

}

} // namespace

不要縮進名字空間:

Warning:

namespace {

// 錯,縮進多余了。

void foo() {

...

}

} // namespace

聲明嵌套命名空間時,每命名空間都獨立成行。

namespace foo {

namespace bar {

8.18. ????

Tip: 水平留白的使用因地制宜. 永遠不要在行尾添加沒意義的留白.

常規:

void f(bool b) { // 左大括號前恒有空格。

...

int i = 0; // 分號前不加空格。

int x[] = { 0 }; // 大括號內部可與空格緊鄰也不可,不過兩邊都要加上。

int x[] = {0};

// 繼承與初始化列表中的冒號前后恒有空格。

class Foo : public Bar {

public:

// 至于內聯函數實現,在大括號內部加上空格并編寫實現。

Foo(int b) : Bar(), baz_(b) {} // 大括號里面是空的話,不加空格。

void Reset() { baz_ = 0; } // 用括號把大括號與實現分開。

...

添加冗余的留白會給其他人編輯時造成額外負擔. 因此, 行尾不要留空格. 如果確定一行代碼已經修改完

畢, 將多余的空格去掉; 或者在專門清理空格時去掉(確信沒有其他人在處理). (Yang.Y 注: 現在大部分代

碼編輯器稍加設置后, 都支持自動刪除行首/行尾空格, 如果不支持, 考慮換一款編輯器或 IDE)

循環和條件語句:

if (b) { // if 條件語句和循環語句關鍵字后均有空格。

} else { // else 前后有空格。

}

while (test) {} // 圓括號內部不緊鄰空格。

switch (i) {

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

switch ( i ) { // 循環和條件語句的圓括號里可以與空格緊鄰。

if ( test ) { // 圓括號,但這很少見。總之要一致。

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

for ( ; i < 5 ; ++i) { // 循環里內 ; 后恒有空格,; 前可以加個空格。

switch (i) {

case 1: // switch case 的冒號前無空格。

...

case 2: break; // 如果冒號有代碼,加個空格。

操作符:

// 賦值操作系統前后恒有空格。

x = 0;

// 其它二元操作符也前后恒有空格,不過對 factors 前后不加空格也可以。

// 圓括號內部不緊鄰空格。

v = w * x + y / z;

v = w*x + y/z;

v = w * (x + z);

// 在參數和一元操作符之間不加空格。

x = -5;

++x;

if (x && !y)

...

模板和轉換:

// 尖叫括號 (< and >) 不與空格緊鄰,< 前沒有空格,>( 之間也沒有。

vector<string> x;

y = static_cast<char*>(x);

// 在類型與指針操作符之間留空格也可以,但要保持一致。

vector<char *> x;

set<list<string>> x; // 在 C++11 代碼里可以這樣用了。

set<list<string> > x; // C++03 中要在 > > 里留個空格。

// 您或許可以在 < < 里加上一對對稱的空格。

set< list<string> > x;

8.19. ????

Tip: 垂直留白越少越好.

這不僅僅是規則而是原則問題了: 不在萬不得已, 不要使用空行. 尤其是: 兩個函數定義之間的空行不要超過 2

行, 函數體首尾不要留空行, 函數體中也不要隨意添加空行.

基本原則是: 同一屏可以顯示的代碼越多, 越容易理解程序的控制流. 當然, 過于密集的代碼塊和過于疏松的代碼

塊同樣難看, 取決于你的判斷. 但通常是垂直留白越少越好.

空行心得如下:

? 函數體內開頭或結尾的空行可讀性微乎其微。

? 在多重 if-else 塊里加空行或許有點可讀性。

??

(YuleFox) ??

1. 對于代碼格式, 因人, 系統而異各有優缺點, 但同一個項目中遵循同一標準還是有必要的;

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

3. 盡量不使用非 ASCII 字符, 如果使用的話, 參考 UTF-8 格式 (尤其是 UNIX/Linux 下, Windows 下可以考慮寬

字符), 盡量不將字符串常量耦合到代碼中, 比如獨立出資源文件, 這不僅僅是風格問題了;

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

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

6. 除函數定義的左大括號可以置于行首外, 包括函數/類/結構體/枚舉聲明, 各種語句的左大括號置于行尾, 所有右

大括號獨立成行;

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

8. 預處理指令/命名空間不使用額外縮進, 類/結構體/枚舉/函數/語句使用縮進;

9. 初始化用 = 還是 () 依個人喜好, 統一就好;

10. return 不要加 ();

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

12. 關于 UNIX/Linux 風格為什么要把左大括號置于行尾 (.cc 文件的函數實現處, 左大括號位于行首), 我的理解是

代碼看上去比較簡約, 想想行首除了函數體被一對大括號封在一起之外, 只有右大括號的代碼看上去確實也舒服;

Windows 風格將左大括號置于行首的優點是匹配情況一目了然.

??δ

acgtyrantε??

1. 80 行限制事實上有助于避免代碼可讀性失控,比如超多重嵌套塊,超多重函數調用等等。

2. Linux 上設置好了 Locale 就幾乎一勞永逸設置好所有開發環境的編碼,不像奇葩的 Windows.

3. Google 強調有一對 if-else 時,不論有沒有嵌套,都要有大括號。Apple 正好 有栽過跟頭 .

4. 其實我主張指針/地址操作符與變量名緊鄰,int* a, b vs int *a, b, 新手會誤以為前者的 b 是 int * 變量,

但后者就不一樣了,高下立判。

5. 在這風格指南里我才剛知道 C++ 原來還有所謂的 Alternative operator representations, 大概沒人用吧。

6. 注意構造函數初始值列表(Constructer Initializer List)與列表初始化(Initializer List)是兩碼事,我就差點

混淆了它們的翻譯。

7. 事實上,如果您熟悉英語本身的書寫規則,就會發現該風格指南在格式上的規定與英語語法相當一脈相承。

比如普通標點符號和單詞后面還有文本的話,總會留一個空格;特殊符號與單詞之間就不用留了,比如 if

(true) 中的圓括號與 true.

8. 本風格指南沒有明確規定 void 函數里要不要用 return 語句,不過就 Google 開源項目 leveldb 并沒有寫;此外

從 Is a blank return statement at the end of a function whos return type is void necessary? 來看,return; 比

return ; 更約定俗成(事實上 cpplint 會對后者報錯,指出分號前有多余的空格),且可用來提前跳出函數棧。

9. ????

前面說明的編程習慣基本都是強制性的. 但所有優秀的規則都允許例外, 這里就是探討這些特例.

9.1. ??у??????

Tip: 對于現有不符合既定編程風格的代碼可以網開一面.

當你修改使用其他風格的代碼時, 為了與代碼原有風格保持一致可以不使用本指南約定. 如果不放心可以與代碼

原作者或現在的負責人員商討, 記住, 一致性 包括原有的一致性.

9.2. Windows ??

Tip: Windows 程序員有自己的編程習慣, 主要源于 Windows 頭文件和其它 Microsoft 代碼. 我們希望任何人都可以

順利讀懂你的代碼, 所以針對所有平臺的 C++ 編程只給出一個單獨的指南.

如果你習慣使用 Windows 編碼風格, 這兒有必要重申一下某些你可能會忘記的指南:

? 不要使用匈牙利命名法 (比如把整型變量命名成 iNum). 使用 Google 命名約定, 包括對源文件使用 .cc 擴展名.

? Windows 定義了很多原生類型的同義詞 (YuleFox 注: 這一點, 我也很反感), 如 DWORD, HANDLE 等等. 在調用

Windows API 時這是完全可以接受甚至鼓勵的. 但還是盡量使用原有的 C++ 類型, 例如, 使用 const TCHAR *

而不是 LPCTSTR.

? 使用 Microsoft Visual C++ 進行編譯時, 將警告級別設置為 3 或更高, 并將所有 warnings 當作 errors 處理.

? 不要使用 #pragma once; 而應該使用 Google 的頭文件保護規則. 頭文件保護的路徑應該相對于項目根目錄

(Yang.Y 注: 如 #ifndef SRC_DIR_BAR_H_, 參考 #define 保護一節).

? 除非萬不得已, 不要使用任何非標準的擴展, 如 #pragma 和 __declspec. 允許使用 __declspec(dllimport) 和

__declspec(dllexport); 但你必須通過宏來使用, 比如 DLLIMPORT 和 DLLEXPORT, 這樣其他人在分享使用這些

代碼時很容易就去掉這些擴展.

在 Windows 上, 只有很少的一些情況下, 我們可以偶爾違反規則:

? 通常我們禁止使用多重繼承, 但在使用 COM 和 ATL/WTL 類時可以使用多重繼承. 為了實現 COM 或

ATL/WTL 類/接口, 你可能不得不使用多重實現繼承.

? 雖然代碼中不應該使用異常, 但是在 ATL 和部分 STL(包括 Visual C++ 的 STL) 中異常被廣泛使用. 使用

ATL 時, 應定義 _ATL_NO_EXCEPTIONS 以禁用異常. 你要研究一下是否能夠禁用 STL 的異常, 如果無法禁用, 啟

用編譯器異常也可以. (注意這只是為了編譯 STL, 自己代碼里仍然不要含異常處理.)

? 通常為了利用頭文件預編譯, 每個每個源文件的開頭都會包含一個名為 StdAfx.h 或 precompile.h 的文件. 為

了使代碼方便與其他項目共享, 避免顯式包含此文件 (precompile.cc), 使用 /FI 編譯器選項以自動包含.

? 資源頭文件通常命名為 resource.h, 且只包含宏的, 不需要遵守本風格指南.

10. ???

Tip: 運用常識和判斷力, 并 保持一致.

編輯代碼時, 花點時間看看項目中的其它代碼, 并熟悉其風格. 如果其它代碼中 if 語句使用空格, 那么你也要使

用. 如果其中的注釋用星號 (*) 圍成一個盒子狀, 你同樣要這么做.

風格指南的重點在于提供一個通用的編程規范, 這樣大家可以把精力集中在實現內容而不是表現形式上. 我們展

示了全局的風格規范, 但局部風格也很重要, 如果你在一個文件中新加的代碼和原有代碼風格相去甚遠, 這就破壞了文

件本身的整體美觀, 也影響閱讀, 所以要盡量避免.

好了, 關于編碼風格寫的夠多了; 代碼本身才更有趣. 盡情享受吧!

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/84055.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/84055.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/84055.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

當科技邂逅浪漫:在Codigger的世界里,遇見“愛”

520&#xff0c;一個充滿愛意的日子&#xff0c;人們用各種方式表達對彼此的深情。而在科技的世界里&#xff0c;我們也正經歷著一場特別的邂逅——Codigger&#xff0c;一個分布式操作系統的誕生&#xff0c;正在以它獨特的方式&#xff0c;重新定義我們與技術的關系。 Codigg…

嵌入式學習筆記 - Void類型的指針

void指針的基本概念和特性 void指針是一種特殊的指針類型&#xff0c;稱為“無類型指針”或“通用指針”。它的主要特點是&#xff1a; ?通用性?&#xff1a;void指針可以指向任何類型的數據&#xff0c;這使得它在處理不確定數據類型時非常有用。 ?靈活性?&#xff1a;由…

【綜述】視頻目標分割VOS

相關連接 更新中....... 1、Associating Objects with Transformers for Video Object Segmentation&#xff1a;論文詳解、AOT源碼解析 2、Rethinking Space-Time Networks with Improved Memory Coverage for Efficient Video Object Segmentation 3、Recurrent Dynamic Embe…

001 嵌入式軟件開發工程師實習篇面試——首戰總結

2025年5月17日人生中第一次面試 緊張是藏不住的。但是不應該的。 目錄 0.準備一份合適的自我介紹 1.結構體內存對齊問題 2.變量在內存中的存儲模式 3.嵌入式中程序框架有哪些 4.程序代碼設計要遵循什原則 5.版本號書寫 6.單片機最小系統板有哪些組成 必須: 非必須:…

SIL2/PLd 認證 Inxpect毫米波安全雷達:3D 掃描 + 微小運動檢測守護工業安全

Inxpect 成立于意大利&#xff0c;專注工業安全技術。自成立起&#xff0c;便致力于借助先進雷達技術提升工業自動化安全標準&#xff0c;解決傳統安全設備在復雜環境中的局限&#xff0c;推出獲 SIL2/PLd 和 UL 認證的安全雷達產品。 Inxpect 的雷達傳感器技術優勢明顯。相較于…

Python數據可視化再探——Matplotlib模塊 之一

目錄 第一章 Matplotlib 模塊教學內容?——基礎圖形繪制 一、Pyplot 子庫介紹? 1. 功能概述? 2. 常用函數? 二、繪制基本圖形? 1. 柱狀圖? 2. 條形圖? 3. 折線圖? 4. 散點圖? 5. 面積圖? 6. 餅狀圖? 7. 圓環圖? ?編輯 三、繪圖知識點詳解? 1. 繪圖…

智慧在線判題OJ系統項目總體,包含功能開發思路,內部中間件,已經部分知識點

目錄 回顧一下xml文件怎么寫 哪個地方使用了哪個技術 MyBatis-Plus-oj的表結構設計&#xff0c; 管理員登錄功能 Swagger Apifox?編輯 BCrypt 日志框架引入(slf4jlogback) nacos Swagger無法被所有微服務獲取到修改的原因 身份認證三種方式: JWT(Json Web Json,一…

使用Spring Boot和Spring Security構建安全的RESTful API

使用Spring Boot和Spring Security構建安全的RESTful API 引言 在現代Web應用開發中&#xff0c;安全性是至關重要的。Spring Boot和Spring Security是Java生態中廣泛使用的框架&#xff0c;它們提供了強大的工具來保護RESTful API。本文將介紹如何結合Spring Boot和Spring S…

虛幻引擎5-Unreal Engine筆記之`GameMode`、`關卡(Level)` 和 `關卡藍圖(Level Blueprint)`的關系

虛幻引擎5-Unreal Engine筆記之GameMode、關卡&#xff08;Level&#xff09; 和 關卡藍圖&#xff08;Level Blueprint&#xff09;的關系 code review! 參考筆記&#xff1a; 1.虛幻引擎5-Unreal Engine筆記之GameMode、關卡&#xff08;Level&#xff09; 和 關卡藍圖&…

Java+Selenium+快代理實現高效爬蟲

目錄 一、前言二、Selenium簡介三、環境準備四、代碼實現4.1 創建WebDriver工廠類4.2 創建爬蟲主類4.3 配置代理的注意事項 六、總結與展望 一、前言 在Web爬蟲技術中&#xff0c;Selenium作為一款強大的瀏覽器自動化工具&#xff0c;能夠模擬真實用戶操作&#xff0c;有效應對…

SpringBoot配置文件的合并

需求:想分類將mysql數據庫的配置放在一個文件,redis的配置放在另外一個文件 就不去引入mysql和redis了,看能否得到值就行了 測試結果 model的包放錯了 應該移動到demo里 能否用yml或者yaml呢 這里注意yml的寫法 測試結果也是可以的 注意如果主配置文件是yml或者yaml的話

深入理解 BFC:網頁布局的關鍵機制

在前端開發的世界里&#xff0c;網頁布局是一項至關重要的任務。而在眾多布局相關的概念中&#xff0c;BFC&#xff08;Block Formatting Context&#xff0c;塊級格式化上下文&#xff09;扮演著極為關鍵的角色。今天&#xff0c;就讓我們深入剖析 BFC 的方方面面。 一、BFC …

04-Web后端基礎(基礎知識)

而像HTML、CSS、JS 以及圖片、音頻、視頻等這些資源&#xff0c;我們都稱為靜態資源。 所謂靜態資源&#xff0c;就是指在服務器上存儲的不會改變的數據&#xff0c;通常不會根據用戶的請求而變化。 那與靜態資源對應的還有一類資源&#xff0c;就是動態資源。那所謂動態資源&…

Vue3 Element Plus el-table-column Sortable 排序失效

問題描述&#xff1a; vue3中 element plus 中 el-table 的 el-table-column使用了插槽后&#xff0c;為什么sortable不起效果&#xff0c;不能點擊排序 <el-table-columnlabel"記賬日期"width"110"fixed"left"header-align"left"…

Unity中SRP Batcher使用整理

SRP Batcher 是一種繪制調用優化,可顯著提高使用 SRP 的應用程序的性能,SRP Batcher 減少了Unity為使用相同著色器變體的材質準備和調度繪制調用所需的CPU 時間。 工作原理: 傳統優化方法通過減少繪制調用次數提升性能,而SRP Batcher的核心理念在于降低繪制調用間的渲染狀…

服務器的基礎知識

什么是服務器 配置牛、運行穩、價格感人的高級計算機&#xff0c;家用電腦不能比擬的。 服務器的組成&#xff1a;電源、raid卡、網卡、內存、cpu、主板、風扇、硬盤。 服務器的分類 按計算能力分類 超級計算機 小型機AIX x86服務器&#xff08;服務器cpu架構&#xff09; …

服務器網絡配置 netplan一個網口配置兩個ip(雙ip、輔助ip、別名IP別名)

文章目錄 問答 問 # This is the network config written by subiquity network:ethernets:enp125s0f0:dhcp4: noaddresses: [192.168.90.180/24]gateway4: 192.168.90.1nameservers:addresses:- 172.0.0.207- 172.0.0.208enp125s0f1:dhcp4: trueenp125s0f2:dhcp4: trueenp125…

高級SQL技巧:時序數據查詢優化與性能調優實戰

高級SQL技巧&#xff1a;時序數據查詢優化與性能調優實戰 引言 在現代數據驅動型系統中&#xff0c;時序數據&#xff08;時間序列數據&#xff09;正成為企業核心資產之一。然而&#xff0c;隨著數據量激增和復雜業務需求的不斷涌現&#xff0c;傳統的SQL查詢方式已難以滿足…

DDoS攻擊應對指南:提升網站安全性的有效策略

DDoS&#xff08;分布式拒絕服務&#xff09;攻擊成為了企業面臨的主要網絡安全威脅之一。隨著技術的不斷發展&#xff0c;DDoS攻擊手段也在不斷升級&#xff0c;給企業的網絡安全帶來了極大的挑戰。針對這一問題&#xff0c;企業需要采取有效的防御措施&#xff0c;以保障網站…

Appium 的 enableMultiWindows 參數

引言 在移動應用自動化測試中&#xff0c;??混合應用&#xff08;Hybrid App&#xff09;?? 和多窗口場景&#xff08;如分屏、彈窗、多 WebView&#xff09;的處理一直是技術難點。Appium 的 enableMultiWindows 參數為這類場景提供了關鍵支持&#xff0c;但在實際使用中常…