原文地址:Google C++ Style Guide
1. 命名空間(Namespaces)
在.cc文件中,提倡使用不具名的命名空間(unnamed namespaces,譯者注:不具名的命名空間就像不具名的類(lèi)一樣,似乎被介紹的很少:-()。使用具名命名空間時(shí),其名稱(chēng)可基于項(xiàng)目或路徑名稱(chēng),不要使用using指示符。
定義:命名空間將全局作用域細(xì)分為不同的、具名的作用域,可有效防止全局作用域的命名沖突。
優(yōu)點(diǎn):命名空間提供了(可嵌套)命名軸線(name axis,譯者注:將命名分割在不同命名空間內(nèi)),當(dāng)然,類(lèi)也提供了(可嵌套)的命名軸線(譯者注:將命名分割在不同類(lèi)的作用域內(nèi))。
舉例來(lái)說(shuō),兩個(gè)不同項(xiàng)目的全局作用域都有一個(gè)類(lèi)Foo,這樣在編譯或運(yùn)行時(shí)造成沖突。如果每個(gè)項(xiàng)目將代碼置于不同命名空間中,project1::Foo和project2::Foo作為不同符號(hào)自然不會(huì)沖突。
缺點(diǎn):命名空間具有迷惑性,因?yàn)樗鼈兒皖?lèi)一樣提供了額外的(可嵌套的)命名軸線。在頭文件中使用不具名的空間容易違背C++的唯一定義原則(One Definition Rule (ODR))。
結(jié)論:根據(jù)下文將要提到的策略合理使用命名空間。
1) 不具名命名空間(Unnamed Namespaces)
在.cc文件中,允許甚至提倡使用不具名命名空間,以避免運(yùn)行時(shí)的命名沖突:
namespace { // .cc 文件中
// 命名空間的內(nèi)容無(wú)需縮進(jìn)
enum { UNUSED, EOF, ERROR }; // 經(jīng)常使用的符號(hào)
bool AtEof() { return pos_ == EOF; } // 使用本命名空間內(nèi)的符號(hào)EOF
} // namespace
然而,與特定類(lèi)關(guān)聯(lián)的文件作用域聲明在該類(lèi)中被聲明為類(lèi)型、靜態(tài)數(shù)據(jù)成員或靜態(tài)成員函數(shù),而不是不具名命名空間的成員。像上文展示的那樣,不具名命名空間結(jié)束時(shí)用注釋// namespace標(biāo)識(shí)。
不能在.h文件中使用不具名命名空間。
2) 具名命名空間(Named Namespaces)
具名命名空間使用方式如下:
命名空間將除文件包含、全局標(biāo)識(shí)的聲明/定義以及類(lèi)的前置聲明外的整個(gè)源文件封裝起來(lái),以同其他命名空間相區(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ì)其他命名空間中類(lèi)的引用等。
#include "a.h"
DEFINE_bool(someflag, false, "dummy flag");
class C; // 全局命名空間中類(lèi)C的前置聲明
namespace a { class A; } // 命名空間a中的類(lèi)a::A的前置聲明
namespace b {
...code for b... // b中的代碼
} // namespace b
不要聲明命名空間std下的任何內(nèi)容,包括標(biāo)準(zhǔn)庫(kù)類(lèi)的前置聲明。聲明std下的實(shí)體會(huì)導(dǎo)致不明確的行為,如,不可移植。聲明標(biāo)準(zhǔn)庫(kù)下的實(shí)體,需要包含對(duì)應(yīng)的頭文件。
最好不要使用using指示符,以保證命名空間下的所有名稱(chēng)都可以正常使用。
// 禁止——污染命名空間
using namespace foo;
在.cc文件、.h文件的函數(shù)、方法或類(lèi)中,可以使用using。
// 允許:.cc文件中
// .h文件中,必須在函數(shù)、方法或類(lèi)的內(nèi)部使用
using ::foo::bar;
在.cc文件、.h文件的函數(shù)、方法或類(lèi)中,還可以使用命名空間別名。
// 允許:.cc文件中
// .h文件中,必須在函數(shù)、方法或類(lèi)的內(nèi)部使用
namespace fbz = ::foo::bar::baz;
2. 嵌套類(lèi)(Nested Class)
當(dāng)公開(kāi)嵌套類(lèi)作為接口的一部分時(shí),雖然可以直接將他們保持在全局作用域中,但將嵌套類(lèi)的聲明置于命名空間中是更好的選擇。
定義:可以在一個(gè)類(lèi)中定義另一個(gè)類(lèi),嵌套類(lèi)也稱(chēng)成員類(lèi)(member class)。
class Foo {
private:
// Bar是嵌套在Foo中的成員類(lèi)
class Bar {
...
};
};
優(yōu)點(diǎn):當(dāng)嵌套(成員)類(lèi)只在被嵌套類(lèi)(enclosing class)中使用時(shí)很有用,將其置于被嵌套類(lèi)作用域作為被嵌套類(lèi)的成員不會(huì)污染其他作用域同名類(lèi)??稍诒磺短最?lèi)中前置聲明嵌套類(lèi),在.cc文件中定義嵌套類(lèi),避免在被嵌套類(lèi)中包含嵌套類(lèi)的定義,因?yàn)榍短最?lèi)的定義通常只與實(shí)現(xiàn)相關(guān)。
缺點(diǎn):只能在被嵌套類(lèi)的定義中才能前置聲明嵌套類(lèi)。因此,任何使用Foo::Bar*指針的頭文件必須包含整個(gè)Foo的聲明。
結(jié)論:不要將嵌套類(lèi)定義為public,除非它們是接口的一部分,比如,某個(gè)方法使用了這個(gè)類(lèi)的一系列選項(xiàng)。
3. 非成員函數(shù)(Nonmember)、靜態(tài)成員函數(shù)(Static Member)和全局函數(shù)(Global Functions)
使用命名空間中的非成員函數(shù)或靜態(tài)成員函數(shù),盡量不要使用全局函數(shù)。
優(yōu)點(diǎn):某些情況下,非成員函數(shù)和靜態(tài)成員函數(shù)是非常有用的,將非成員函數(shù)置于命名空間中可避免對(duì)全局作用域的污染。
缺點(diǎn):將非成員函數(shù)和靜態(tài)成員函數(shù)作為新類(lèi)的成員或許更有意義,當(dāng)它們需要訪問(wèn)外部資源或具有重要依賴時(shí)更是如此。
結(jié)論:
有時(shí),不把函數(shù)限定在類(lèi)的實(shí)體中是有益的,甚至需要這么做,要么作為靜態(tài)成員,要么作為非成員函數(shù)。非成員函數(shù)不應(yīng)依賴于外部變量,并盡量置于某個(gè)命名空間中。相比單純?yōu)榱朔庋b若干不共享任何靜態(tài)數(shù)據(jù)的靜態(tài)成員函數(shù)而創(chuàng)建類(lèi),不如使用命名空間。
定義于同一編譯單元的函數(shù),被其他編譯單元直接調(diào)用可能會(huì)引入不必要的耦合和連接依賴;靜態(tài)成員函數(shù)對(duì)此尤其敏感。可以考慮提取到新類(lèi)中,或者將函數(shù)置于獨(dú)立庫(kù)的命名空間中。
如果你確實(shí)需要定義非成員函數(shù),又只是在.cc文件中使用它,可使用不具名命名空間或static關(guān)聯(lián)(如static int Foo() {...}
)限定其作用域。
4. 局部變量(Local Variables)
將函數(shù)變量盡可能置于最小作用域內(nèi),在聲明變量時(shí)將其初始化。
C++允許在函數(shù)的任何位置聲明變量。我們提倡在盡可能小的作用域中聲明變量,離第一次使用越近越好。這使得代碼易于閱讀,易于定位變量的聲明位置、變量類(lèi)型和初始值。特別是,應(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等語(yǔ)句中,作用域聲明(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);
}
類(lè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類(lèi)型的全局變量是被禁止的,內(nèi)建類(lèi)型的全局變量是允許的,當(dāng)然多線程代碼中非常數(shù)全局變量也是被禁止的。永遠(yuǎn)不要使用函數(shù)返回值初始化全局變量。
不幸的是,全局變量的構(gòu)造函數(shù)、析構(gòu)函數(shù)以及初始化操作的調(diào)用順序只是被部分規(guī)定,每次生成有可能會(huì)有變化,從而導(dǎo)致難以發(fā)現(xiàn)的bugs。
因此,禁止使用class類(lèi)型的全局變量(包括STL的string, vector等等),因?yàn)樗鼈兊某跏蓟樞蛴锌赡軐?dǎo)致構(gòu)造出現(xiàn)問(wèn)題。內(nèi)建類(lèi)型和由內(nèi)建類(lèi)型構(gòu)成的沒(méi)有構(gòu)造函數(shù)的結(jié)構(gòu)體可以使用,如果你一定要使用class類(lèi)型的全局變量,請(qǐng)使用單件模式(singleton pattern)。
對(duì)于全局的字符串常量,使用C風(fēng)格的字符串,而不要使用STL的字符串:
const char kFrogSays[] = "ribbet";
雖然允許在全局作用域中使用全局變量,使用時(shí)務(wù)必三思。大多數(shù)全局變量應(yīng)該是類(lèi)的靜態(tài)數(shù)據(jù)成員,或者當(dāng)其只在.cc文件中使用時(shí),將其定義到不具名命名空間中,或者使用靜態(tài)關(guān)聯(lián)以限制變量的作用域。
記住,靜態(tài)成員變量視作全局變量,所以,也不能是class類(lèi)型!
______________________________________
譯者:這一篇主要提到的是作用域的一些規(guī)則,總結(jié)一下:
1. .cc中的不具名命名空間可避免命名沖突、限定作用域,避免直接使用using提示符污染命名空間;
2. 嵌套類(lèi)符合局部使用原則,只是不能在其他頭文件中前置聲明,盡量不要public;
3. 盡量不用全局函數(shù)和全局變量,考慮作用域和命名空間限制,盡量單獨(dú)形成編譯單元;
4. 多線程中的全局變量(含靜態(tài)成員變量)不要使用class類(lèi)型(含STL容器),避免不明確行為導(dǎo)致的bugs。
作用域的使用,除了考慮名稱(chēng)污染、可讀性之外,主要是為降低耦合度,提高編譯、執(zhí)行效率。