Tip
鼓勵(lì)在
.cc
文件內(nèi)使用匿名名字空間. 使用具名的名字空間時(shí), 其名稱可基于項(xiàng)目名或相對(duì)路徑. 不要使用 using 關(guān)鍵字.
定義:名字空間將全局作用域細(xì)分為獨(dú)立的, 具名的作用域, 可有效防止全局作用域的命名沖突.優(yōu)點(diǎn):
雖然類已經(jīng)提供了(可嵌套的)命名軸線 (YuleFox 注: 將命名分割在不同類的作用域內(nèi)), 名字空間在這基礎(chǔ)上又封裝了一層.
舉例來說, 兩個(gè)不同項(xiàng)目的全局作用域都有一個(gè)類 Foo
, 這樣在編譯或運(yùn)行時(shí)造成沖突. 如果每個(gè)項(xiàng)目將代碼置于不同名字空間中, project1::Foo
和 project2::Foo
作為不同符號(hào)自然不會(huì)沖突.
缺點(diǎn):
名字空間具有迷惑性, 因?yàn)樗鼈兒皖愐粯犹峁┝祟~外的 (可嵌套的) 命名軸線.
在頭文件中使用匿名空間導(dǎo)致違背 C++ 的唯一定義原則 (One Definition Rule (ODR)).
結(jié)論:根據(jù)下文將要提到的策略合理使用命名空間.
.cc
文件中, 允許甚至鼓勵(lì)使用匿名名字空間, 以避免運(yùn)行時(shí)的命名沖突:namespace { // .cc 文件中
// 名字空間的內(nèi)容無需縮進(jìn)
enum { kUNUSED, kEOF, kERROR }; // 經(jīng)常使用的符號(hào)
bool AtEof() { return pos_ == kEOF; } // 使用本名字空間內(nèi)的符號(hào) EOF
} // namespace
然而, 與特定類關(guān)聯(lián)的文件作用域聲明在該類中被聲明為類型, 靜態(tài)數(shù)據(jù)成員或靜態(tài)成員函數(shù), 而不是匿名名字空間的成員. 如上例所示, 匿名空間結(jié)束時(shí)用注釋 // namespace
標(biāo)識(shí).
不要在 .h
文件中使用匿名名字空間.
具名的名字空間使用方式如下:
- 用名字空間把文件包含, gflags [http://code.google.com/p/google-gflags/] 的聲明/定義, 以及類的前置聲明以外的整個(gè)源文件封裝起來, 以區(qū)別于其它名字空間:> > // .h 文件
namespace mynamespace {
// 所有聲明都置于命名空間中
// 注意不要使用縮進(jìn)
class MyClass {
public:
…
void Foo();
};
} // namespace mynamespace
// .cc 文件
namespace mynamespace {
// 函數(shù)定義都置于命名空間中
void MyClass::Foo() {
…
}
} // namespace mynamespace
> 通常的 `.cc` 文件包含更多, 更復(fù)雜的細(xì)節(jié), 比如引用其他名字空間的類等.
> > #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` 內(nèi)聲明任何東西, 包括標(biāo)準(zhǔn)庫的類前置聲明. 在 `std` 名字空間聲明實(shí)體會(huì)導(dǎo)致不確定的問題, 比如不可移植. 聲明標(biāo)準(zhǔn)庫下的實(shí)體, 需要包含對(duì)應(yīng)的頭文件. 最好不要使用 ``using`` 關(guān)鍵字, 以保證名字空間下的所有名稱都可以正常使用. // 禁止 —— 污染名字空間
using namespace foo;
在 `.cc` 文件, `.h` 文件的函數(shù), 方法或類中, 可以使用 _``using`` 關(guān)鍵字_.
> > > // 允許: .cc 文件中
// .h 文件的話, 必須在函數(shù), 方法或類的內(nèi)部使用
using ::foo::bar;
在 `.cc` 文件, `.h` 文件的函數(shù), 方法或類中, 允許使用名字空間別名.
> > > // 允許: .cc 文件中
// .h 文件的話, 必須在函數(shù), 方法或類的內(nèi)部使用
namespace fbz = ::foo::bar::baz;
## 2.2. 嵌套類
> Tip
> 當(dāng)公有嵌套類作為接口的一部分時(shí), 雖然可以直接將他們保持在全局作用域中, 但將嵌套類的聲明置于名字空間內(nèi)是更好的選擇.
定義: 在一個(gè)類內(nèi)部定義另一個(gè)類; 嵌套類也被稱為 _成員類 (member class)_.
class Foo {
private:
// Bar是嵌套在Foo中的成員類
class Bar {
…
};
};
優(yōu)點(diǎn): 當(dāng)嵌套 (或成員) 類只被外圍類使用時(shí)非常有用; 把它作為外圍類作用域內(nèi)的成員, 而不是去污染外部作用域的同名類. 嵌套類可以在外圍類中做前置聲明, 然后在 `.cc` 文件中定義, 這樣避免在外圍類的聲明中定義嵌套類, 因?yàn)榍短最惖亩x通常只與實(shí)現(xiàn)相關(guān). 缺點(diǎn): 嵌套類只能在外圍類的內(nèi)部做前置聲明. 因此, 任何使用了 `Foo::Bar*` 指針的頭文件不得不包含類 `Foo` 的整個(gè)聲明. 結(jié)論: 不要將嵌套類定義成公有, 除非它們是接口的一部分, 比如, 嵌套類含有某些方法的一組選項(xiàng).
## 2.3. 非成員函數(shù)、靜態(tài)成員函數(shù)和全局函數(shù)
> Tip
> 使用靜態(tài)成員函數(shù)或名字空間內(nèi)的非成員函數(shù), 盡量不要用裸的全局函數(shù).
優(yōu)點(diǎn):
某些情況下, 非成員函數(shù)和靜態(tài)成員函數(shù)是非常有用的, 將非成員函數(shù)放在名字空間內(nèi)可避免污染全局作用域.
缺點(diǎn):
將非成員函數(shù)和靜態(tài)成員函數(shù)作為新類的成員或許更有意義, 當(dāng)它們需要訪問外部資源或具有重要的依賴關(guān)系時(shí)更是如此.
結(jié)論:
有時(shí), 把函數(shù)的定義同類的實(shí)例脫鉤是有益的, 甚至是必要的. 這樣的函數(shù)可以被定義成靜態(tài)成員, 或是非成員函數(shù). 非成員函數(shù)不應(yīng)依賴于外部變量, 應(yīng)盡量置于某個(gè)名字空間內(nèi). 相比單純?yōu)榱朔庋b若干不共享任何靜態(tài)數(shù)據(jù)的靜態(tài)成員函數(shù)而創(chuàng)建類, 不如使用命名空間.
定義在同一編譯單元的函數(shù), 被其他編譯單元直接調(diào)用可能會(huì)引入不必要的耦合和鏈接時(shí)依賴; 靜態(tài)成員函數(shù)對(duì)此尤其敏感. 可以考慮提取到新類中, 或者將函數(shù)置于獨(dú)立庫的名字空間內(nèi).
如果你必須定義非成員函數(shù), 又只是在?`.cc`?文件中使用它, 可使用匿名名字空間或?`static`?鏈接關(guān)鍵字 (如?`static?int?Foo()?{...}`) 限定其作用域.
## 2.4. 局部變量
> Tip
> 將函數(shù)變量盡可能置于最小作用域內(nèi), 并在變量聲明時(shí)進(jìn)行初始化.
C++ 允許在函數(shù)的任何位置聲明變量. 我們提倡在盡可能小的作用域中聲明變量, 離第一次使用越近越好. 這使得代碼瀏覽者更容易定位變量聲明的位置, 了解變量的類型和初始值. 特別是,應(yīng)使用初始化的方式替代聲明再賦值, 比如:
int i;
i = f(); // 壞——初始化和聲明分離
int j = g(); // 好——初始化時(shí)聲明
注意, GCC 可正確實(shí)現(xiàn)了?`for?(int?i?=?0;?i??10;?++i)`?(`i`?的作用域僅限?`for`?循環(huán)內(nèi)), 所以其他?`for`?循環(huán)中可以重新使用?`i`. 在?`if`?和?`while`?等語句中的作用域聲明也是正確的, 如:
while (const char* p = strchr(str, ‘/’)) str = p + 1;
> Warning
> 如果變量是一個(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);
}
## 2.5. 靜態(tài)和全局變量
> Tip
> 禁止使用 `class` 類型的靜態(tài)或全局變量: 它們會(huì)導(dǎo)致很難發(fā)現(xiàn)的 bug 和不確定的構(gòu)造和析構(gòu)函數(shù)調(diào)用順序.
靜態(tài)生存周期的對(duì)象, 包括全局變量, 靜態(tài)變量, 靜態(tài)類成員變量, 以及函數(shù)靜態(tài)變量, 都必須是原生數(shù)據(jù)類型 (POD : Plain Old Data): 只能是 int, char, float, 和 void, 以及 POD 類型的數(shù)組/結(jié)構(gòu)體/指針. 永遠(yuǎn)不要使用函數(shù)返回值初始化靜態(tài)變量; 不要在多線程代碼中使用非 `const` 的靜態(tài)變量.
不幸的是, 靜態(tài)變量的構(gòu)造函數(shù), 析構(gòu)函數(shù)以及初始化操作的調(diào)用順序在 C++ 標(biāo)準(zhǔn)中未明確定義, 甚至每次編譯構(gòu)建都有可能會(huì)發(fā)生變化, 從而導(dǎo)致難以發(fā)現(xiàn)的 bug. 比如, 結(jié)束程序時(shí), 某個(gè)靜態(tài)變量已經(jīng)被析構(gòu)了, 但代碼還在跑 – 其它線程很可能 – 試圖訪問該變量, 直接導(dǎo)致崩潰.
所以, 我們只允許 POD 類型的靜態(tài)變量. 本條規(guī)則完全禁止 `vector` (使用 C 數(shù)組替代), `string` (使用 `const char*`), 及其它以任意方式包含或指向類實(shí)例的東東, 成為靜態(tài)變量. 出于同樣的理由, 我們不允許用函數(shù)返回值來初始化靜態(tài)變量.
如果你確實(shí)需要一個(gè) `class` 類型的靜態(tài)或全局變量, 可以考慮在 ``main()` 函數(shù)或 `pthread_once()` 內(nèi)初始化一個(gè)你永遠(yuǎn)不會(huì)回收的指針.
> Note
> yospaly 譯注:
> 上文提及的靜態(tài)變量泛指靜態(tài)生存周期的對(duì)象, 包括: 全局變量, 靜態(tài)變量, 靜態(tài)類成員變量, 以及函數(shù)靜態(tài)變量.
## 譯者 (YuleFox) 筆記
1. `cc` 中的匿名名字空間可避免命名沖突, 限定作用域, 避免直接使用 `using` 關(guān)鍵字污染命名空間;
1. 嵌套類符合局部使用原則, 只是不能在其他頭文件中前置聲明, 盡量不要 `public`;
1. 盡量不用全局函數(shù)和全局變量, 考慮作用域和命名空間限制, 盡量單獨(dú)形成編譯單元;
1. 多線程中的全局變量 (含靜態(tài)成員變量) 不要使用 `class` 類型 (含 STL 容器), 避免不明確行為導(dǎo)致的 bug.
1. 作用域的使用, 除了考慮名稱污染, 可讀性之外, 主要是為降低耦合, 提高編譯/執(zhí)行效率.
更多建議: