鍍金池/ 教程/ C/ 作用域
結(jié)束語
作用域
頭文件
來自 Google 的奇技
注釋
規(guī)則特例
命名約定
其他 C++ 特性
格式

作用域

名字空間

Tip
    鼓勵在 ``.cc`` 文件內(nèi)使用匿名名字空間. 使用具名的名字空間時, 其名稱可基于項目名或相對路徑. 不要使用 *using 關(guān)鍵字*.

定義: 名字空間將全局作用域細(xì)分為獨立的, 具名的作用域, 可有效防止全局作用域的命名沖突.

優(yōu)點: 雖然類已經(jīng)提供了(可嵌套的)命名軸線 (YuleFox 注: 將命名分割在不同類的作用域內(nèi)), 名字空間在這基礎(chǔ)上又封裝了一層.

舉例來說, 兩個不同項目的全局作用域都有一個類 ``Foo``, 這樣在編譯或運行時造成沖突. 如果每個項目將代碼置于不同名字空間中, ``project1::Foo`` 和 ``project2::Foo`` 作為不同符號自然不會沖突.

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

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

結(jié)論: 根據(jù)下文將要提到的策略合理使用命名空間.

匿名名字空間

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

        // 名字空間的內(nèi)容無需縮進
        enum { kUNUSED, kEOF, kERROR };         // 經(jīng)常使用的符號
        bool AtEof() { return pos_ == kEOF; }   // 使用本名字空間內(nèi)的符號 EOF

        } // namespace

然而, 與特定類關(guān)聯(lián)的文件作用域聲明在該類中被聲明為類型, 靜態(tài)數(shù)據(jù)成員或靜態(tài)成員函數(shù), 而不是匿名名字空間的成員. 如上例所示, 匿名空間結(jié)束時用注釋 // namespace 標(biāo)識.

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

具名的名字空間

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

  • 用名字空間把文件包含, gflags <http://code.google.com/p/google-gflags/>_ 的聲明/定義, 以及類的前置聲明以外的整個源文件封裝起來, 以區(qū)別于其它名字空間:
            // .h 文件
            namespace mynamespace {

            // 所有聲明都置于命名空間中
            // 注意不要使用縮進
            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 名字空間聲明實體會導(dǎo)致不確定的問題, 比如不可移植. 聲明標(biāo)準(zhǔn)庫下的實體, 需要包含對應(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;

嵌套類

Tip
    當(dāng)公有嵌套類作為接口的一部分時, 雖然可以直接將他們保持在全局作用域中, 但將嵌套類的聲明置于名字空間內(nèi)是更好的選擇.

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

        class Foo {

        private:
            // Bar是嵌套在Foo中的成員類
            class Bar {
                …
            };

        };

優(yōu)點: 當(dāng)嵌套 (或成員) 類只被外圍類使用時非常有用; 把它作為外圍類作用域內(nèi)的成員, 而不是去污染外部作用域的同名類. 嵌套類可以在外圍類中做前置聲明, 然后在 .cc 文件中定義, 這樣避免在外圍類的聲明中定義嵌套類, 因為嵌套類的定義通常只與實現(xiàn)相關(guān).

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

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

非成員函數(shù)、靜態(tài)成員函數(shù)和全局函數(shù)

Tip
    使用靜態(tài)成員函數(shù)或名字空間內(nèi)的非成員函數(shù), 盡量不要用裸的全局函數(shù).

優(yōu)點: 某些情況下, 非成員函數(shù)和靜態(tài)成員函數(shù)是非常有用的, 將非成員函數(shù)放在名字空間內(nèi)可避免污染全局作用域.

缺點: 將非成員函數(shù)和靜態(tài)成員函數(shù)作為新類的成員或許更有意義, 當(dāng)它們需要訪問外部資源或具有重要的依賴關(guān)系時更是如此.

結(jié)論: 有時, 把函數(shù)的定義同類的實例脫鉤是有益的, 甚至是必要的. 這樣的函數(shù)可以被定義成靜態(tài)成員, 或是非成員函數(shù). 非成員函數(shù)不應(yīng)依賴于外部變量, 應(yīng)盡量置于某個名字空間內(nèi). 相比單純?yōu)榱朔庋b若干不共享任何靜態(tài)數(shù)據(jù)的靜態(tài)成員函數(shù)而創(chuàng)建類, 不如使用命名空間.

定義在同一編譯單元的函數(shù), 被其他編譯單元直接調(diào)用可能會引入不必要的耦合和鏈接時依賴; 靜態(tài)成員函數(shù)對此尤其敏感. 可以考慮提取到新類中, 或者將函數(shù)置于獨立庫的名字空間內(nèi).

如果你必須定義非成員函數(shù), 又只是在 ``.cc`` 文件中使用它, 可使用匿名名字空間或 ``static`` 鏈接關(guān)鍵字 (如 ``static int Foo() {...}``) 限定其作用域.

局部變量

Tip
    將函數(shù)變量盡可能置于最小作用域內(nèi), 并在變量聲明時進行初始化.

C++ 允許在函數(shù)的任何位置聲明變量. 我們提倡在盡可能小的作用域中聲明變量, 離第一次使用越近越好. 這使得代碼瀏覽者更容易定位變量聲明的位置, 了解變量的類型和初始值. 特別是,應(yīng)使用初始化的方式替代聲明再賦值, 比如:

        int i;
        i = f(); // 壞——初始化和聲明分離
        int j = g(); // 好——初始化時聲明

注意, GCC 可正確實現(xiàn)了 for (int i = 0; i < 10; ++i) (i 的作用域僅限 for 循環(huán)內(nèi)), 所以其他 for 循環(huán)中可以重新使用 i. 在 ifwhile 等語句中的作用域聲明也是正確的, 如:

        while (const char* p = strchr(str, ‘/’)) str = p + 1;
Warning
如果變量是一個對象, 每次進入作用域都要調(diào)用其構(gòu)造函數(shù), 每次退出作用域都要調(diào)用其析構(gòu)函數(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);
        }

靜態(tài)和全局變量

Tip
禁止使用 ``class`` 類型的靜態(tài)或全局變量: 它們會導(dǎo)致很難發(fā)現(xiàn)的 bug 和不確定的構(gòu)造和析構(gòu)函數(shù)調(diào)用順序.

靜態(tài)生存周期的對象, 包括全局變量, 靜態(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)建都有可能會發(fā)生變化, 從而導(dǎo)致難以發(fā)現(xiàn)的 bug. 比如, 結(jié)束程序時, 某個靜態(tài)變量已經(jīng)被析構(gòu)了, 但代碼還在跑 -- 其它線程很可能 -- 試圖訪問該變量, 直接導(dǎo)致崩潰.

所以, 我們只允許 POD 類型的靜態(tài)變量. 本條規(guī)則完全禁止 vector (使用 C 數(shù)組替代), string (使用 const char*), 及其它以任意方式包含或指向類實例的東東, 成為靜態(tài)變量. 出于同樣的理由, 我們不允許用函數(shù)返回值來初始化靜態(tài)變量.

如果你確實需要一個 class` 類型的靜態(tài)或全局變量, 可以考慮在main()函數(shù)或pthread_once()`` 內(nèi)初始化一個你永遠(yuǎn)不會回收的指針.

Note

yospaly 譯注:

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

譯者 (YuleFox) 筆記

  1. cc 中的匿名名字空間可避免命名沖突, 限定作用域, 避免直接使用 using 關(guān)鍵字污染命名空間;
  2. 嵌套類符合局部使用原則, 只是不能在其他頭文件中前置聲明, 盡量不要 public;
  3. 盡量不用全局函數(shù)和全局變量, 考慮作用域和命名空間限制, 盡量單獨形成編譯單元;
  4. 多線程中的全局變量 (含靜態(tài)成員變量) 不要使用 class 類型 (含 STL 容器), 避免不明確行為導(dǎo)致的 bug.
  5. 作用域的使用, 除了考慮名稱污染, 可讀性之外, 主要是為降低耦合, 提高編譯/執(zhí)行效率.
上一篇:下一篇:命名約定