Google C++程式設計風格指南(二):作用域

yangdelong發表於2009-10-10

http://www.kuqin.com/language/20080714/11275.html

 

1. .cc中的不具名名稱空間可避免命名衝突、限定作用域,避免直接使用using提示符汙染名稱空間; 2. 巢狀類符合區域性使用原則,只是不能在其他標頭檔案中前置宣告,儘量不要public;3. 儘量不用全域性函式和全域性變數,考慮作用域和名稱空間限制,儘量單獨形成編譯單元……

 

 

  • 作用域

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();        // 壞——初始化和宣告分離
nt 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.cppblog.com/Fox/archive/2008/07/14/56109.html
原文:Google C++ Style Guide

相關文章