範圍
1. 命名空間(Namespaces)
在.cc檔案中,提倡使用不具名的命名空間(unnamed namespaces,譯者註:不具名的命名空間就像不具名的類一樣,似乎被介紹的很少:-()。使用具名命名空間時,其名稱可基於項目或路徑名稱,不要使用using指示符。
定義:命名空間將全域範圍細分為不同的、具名的範圍,可有效防止全域範圍的命名衝突。
優點:命名空間提供了(可嵌套)命名軸線(name axis,譯者註:將命名分割在不同命名空間內),當然,類也提供了(可嵌套)的命名軸線(譯者註:將命名分割在不同類的範圍內)。
舉例來說,兩個不同項目的全域範圍都有一個類Foo,這樣在編譯或運行時造成衝突。如果每個項目將代碼置於不同命名空間中,project1::Foo和project2::Foo作為不同符號自然不會衝突。
缺點:命名空間具有迷惑性,因為它們和類一樣提供了額外的(可嵌套的)命名軸線。在標頭檔中使用不具名的空間容易違背C++的唯一定義原則(One Definition Rule (ODR))。
結論:根據下文將要提到的策略合理使用命名空間。
1) 不具名命名空間(Unnamed Namespaces)
在.cc檔案中,允許甚至提倡使用不具名命名空間,以避免運行時的命名衝突:
namespace { // .cc 檔案中
// 命名空間的內容無需縮排
enum { UNUSED, EOF, ERROR }; // 經常使用的符號
bool AtEof() { return pos_ == EOF; } // 使用本命名空間內的符號EOF
} // namespace
然而,與特定類關聯的檔案範圍聲明在該類中被聲明為類型、待用資料成員或靜態成員函數,而不是不具名命名空間的成員。像上文展示的那樣,不具名命名空間結束時用注釋// namespace標識。
不能在.h檔案中使用不具名命名空間。
2) 具名命名空間(Named Namespaces)
具名命名空間使用方式如下:
命名空間將除檔案包含、全域標識的聲明/定義以及類的前置聲明外的整個源檔案封裝起來,以同其他命名空間相區分。
// .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::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;
2. 嵌套類(Nested Class)
當公開嵌套類作為介面的一部分時,雖然可以直接將他們保持在全域範圍中,但將嵌套類的聲明置於命名空間中是更好的選擇。
定義:可以在一個類中定義另一個類,嵌套類也稱成員類(member class)。
class Foo {
private:
// Bar是嵌套在Foo中的成員類
class Bar {
...
};
};
優點:當嵌套(成員)類只在被嵌套類(enclosing class)中使用時很有用,將其置於被嵌套類範圍作為被嵌套類的成員不會汙染其他範圍同名類。可在被嵌套類中前置聲明嵌套類,在.cc檔案中定義嵌套類,避免在被嵌套類中包含嵌套類的定義,因為嵌套類的定義通常只與實現相關。
缺點:只能在被嵌套類的定義中才能前置聲明嵌套類。因此,任何使用Foo::Bar*指標的標頭檔必須包含整個Foo的聲明。
結論:不要將嵌套類定義為public,除非它們是介面的一部分,比如,某個方法使用了這個類的一系列選項。
3. 非成員函數(Nonmember)、靜態成員函數(Static Member)和全域函數(Global Functions)
使用命名空間中的非成員函數或靜態成員函數,盡量不要使用全域函數。
優點:某些情況下,非成員函數和靜態成員函數是非常有用的,將非成員函數置於命名空間中可避免對全域範圍的汙染。
缺點:將非成員函數和靜態成員函數作為新類的成員或許更有意義,當它們需要訪問外部資源或具有重要依賴時更是如此。
結論:
有時,不把函數限定在類的實體中是有益的,甚至需要這麼做,要麼作為靜態成員,要麼作為非成員函數。非成員函數不應依賴於外部變數,並盡量置於某個命名空間中。相比單純為了封裝若干不共用任何待用資料的靜態成員函數而建立類,不如使用命名空間。
定義於同一編譯單元的函數,被其他編譯單元直接調用可能會引入不必要的耦合和串連依賴;靜態成員函數對此尤其敏感。可以考慮提取到新類中,或者將函數置於獨立庫的命名空間中。
如果你確實需要定義非成員函數,又只是在.cc檔案中使用它,可使用不具名命名空間或static關聯(如static int Foo() {...})限定其範圍。
4. 局部變數(Local Variables)
將函數變數儘可能置於最小範圍內,在聲明變數時將其初始化。
C++允許在函數的任何位置聲明變數。我們提倡在儘可能小的範圍中聲明變數,離第一次使用越近越好。這使得代碼易於閱讀,易於定位變數的聲明位置、變數類型和初始值。特別是,應使用初始化代替聲明+賦值的方式。
int i;
i = f(); // 壞——初始化和聲明分離
int j = g(); // 好——初始化時聲明
注意:gcc可正確執行for (int i = 0; i < 10; ++i)(i的範圍僅限for迴圈),因此其他for迴圈中可重用i。if和while等語句中,範圍聲明(scope declaration)同樣是正確的。
while (const char* p = strchr(str, '/')) str = p + 1;
注意:如果變數是一個對象,每次進入範圍都要調用其建構函式,每次退出範圍都要調用其解構函式。
// 低效的實現
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);
}
5. 全域變數(Global Variables)
class類型的全域變數是被禁止的,內建類型的全域變數是允許的,當然多線程代碼中非常數全域變數也是被禁止的。永遠不要使用函數傳回值初始化全域變數。
不幸的是,全域變數的建構函式、解構函式以及初始化操作的調用順序只是被部分規定,每次產生有可能會有變化,從而導致難以發現的bugs。
因此,禁止使用class類型的全域變數(包括STL的string, vector等等),因為它們的初始化順序有可能導致構造出現問題。內建類型和由內建類型構成的沒有建構函式的結構體可以使用,如果你一定要使用 class類型的全域變數,請使用單件模式(singleton pattern)。
對於全域的字串常量,使用C風格的字串,而不要使用STL的字串:
const char kFrogSays[] = "ribbet";
雖然允許在全域範圍中使用全域變數,使用時務必三思。大多數全域變數應該是類的待用資料成員,或者當其只在.cc檔案中使用時,將其定義到不具名命名空間中,或者使用靜態關聯以限制變數的範圍。
記住,靜態成員變數視作全域變數,所以,也不能是class類型!
譯者:這一篇主要提到的是範圍的一些規則,總結一下:
1. .cc中的不具名命名空間可避免命名衝突、限定範圍,避免直接使用using提示符汙染命名空間;
2. 嵌套類符合局部使用原則,只是不能在其他標頭檔中前置聲明,盡量不要public;
3. 盡量不用全域函數和全域變數,考慮範圍和命名空間限制,盡量單獨形成編譯單元;
4. 多線程中的全域變數(含靜態成員變數)不要使用class類型(含STL容器),避免不明確行為導致的bugs。
範圍的使用,除了考慮名稱汙染、可讀性之外,主要是為降低耦合度,提高編譯、執行效率。
本文來自編程入門網:http://www.bianceng.cn/Programming/cplus/201009/19334.htm