命名空間
命名空間提供一種方法,用於防止大型專案中的名稱衝突。
在命名空間區塊內宣告的實體會被置於命名空間作用域中,這可以防止它們被誤認為是其他作用域中同名的實體。
在所有命名空間區塊之外宣告的實體屬於全域命名空間。全域命名空間屬於全域作用域,並可以使用前置的::明確引用。儘管它沒有宣告,全域命名空間並不是一個匿名命名空間。
允許存在多個同名的命名空間區塊。這些區塊內的所有宣告都宣告在相同的命名空間作用域中。
目錄 |
[編輯] 語法
namespace 命名空間名稱 { 宣告 } |
(1) | ||||||||
inline namespace 命名空間名稱 { 宣告 } |
(2) | (C++11 起) | |||||||
namespace { 宣告 } |
(3) | ||||||||
命名空間名稱 :: 成員名稱 |
(4) | ||||||||
using namespace 命名空間名稱 ; |
(5) | ||||||||
using 命名空間名稱 :: 成員名稱 ; |
(6) | ||||||||
namespace 名稱 = 限定命名空間 ; |
(7) | ||||||||
namespace 命名空間名稱 :: 成員名稱 { 宣告 } |
(8) | (自 C++17 起) | |||||||
namespace 命名空間名稱 :: inline 成員名稱 { 宣告 } |
(9) | (自 C++20 起) | |||||||
[編輯] 解釋
[編輯] 命名空間
inline(選用) namespace 屬性 (選用) 識別字 { 命名空間主體 } |
|||||||||
inline
|
- | (C++11 起) 如果存在,則使其成為行內命名空間(見下文)。如果原始命名空間定義未使用 inline,則不能出現在擴展命名空間定義上 | ||
| 屬性 | - | (C++17 起) 任意數量的屬性的選用序列 | ||
| 識別字 | - | 其中之一:
| ||
| 命名空間主體 | - | 可能為空的任何種類的宣告序列(包括類別和函數定義以及巢狀命名空間) |
命名空間定義只允許出現在命名空間作用域中,包括全域作用域。
要重新開啟一個現有命名空間(正式而言,作為一個擴展命名空間定義),命名空間定義中使用的 識別字 的查詢必須解析為一個命名空間名稱(而非命名空間別名),該名稱已宣告為封閉命名空間的成員,或封閉命名空間內的行內命名空間的成員。
在 命名空間主體 內出現的宣告(包括巢狀命名空間定義)所引入的所有名稱,都成為命名空間 識別字 的成員,無論此命名空間定義是原始命名空間定義(引入了 識別字),還是擴展命名空間定義(「重新開啟」了已定義的命名空間)。
在命名空間主體內宣告的命名空間成員,可以使用明確限定符在其外部定義或重新宣告。
namespace Q { namespace V // V is a member of Q, and is fully defined within Q { // namespace Q::V { // C++17 alternative to the lines above class C { void m(); }; // C is a member of V and is fully defined within V // C::m is only declared void f(); // f is a member of V, but is only declared here } void V::f() // definition of V's member f outside of V // f's enclosing namespaces are still the global namespace, Q, and Q::V { extern void h(); // This declares ::Q::V::h } void V::C::m() // definition of V::C::m outside of the namespace (and the class body) // enclosing namespaces are the global namespace, Q, and Q::V {} }
命名空間外部的定義和重新宣告只允許出現在
- 宣告點之後,
- 在命名空間作用域中,以及
- 包含原始命名空間的命名空間中(包括全域命名空間)。
此外,它們必須使用限定識別碼(qualified-id)語法。
namespace Q { namespace V // original-namespace-definition for V { void f(); // declaration of Q::V::f } void V::f() {} // OK void V::g() {} // Error: g() is not yet a member of V namespace V // extension-namespace-definition for V { void g(); // declaration of Q::V::g } } namespace R // not an enclosing namespace for Q { void Q::V::g() {} // Error: cannot define Q::V::g inside R } void Q::V::g() {} // OK: global namespace encloses Q
在非局部類別 X 內由friend宣告引入的名稱會成為 X 最內部封閉命名空間的成員,但它們不會對一般名稱查詢(無論是非限定還是限定)可見,除非在命名空間作用域中提供了匹配的宣告,無論是在類別定義之前還是之後。此類名稱可透過ADL找到,ADL 會考慮命名空間和類別。
只有最內部封閉命名空間會被此類 friend 宣告考慮,以決定名稱是否會與先前宣告的名稱衝突。
void h(int); namespace A { class X { friend void f(X); // A::f is a friend class Y { friend void g(); // A::g is a friend friend void h(int); // A::h is a friend, no conflict with ::h }; }; // A::f, A::g and A::h are not visible at namespace scope // even though they are members of the namespace A X x; void g() // definition of A::g { f(x); // A::X::f is found through ADL } void f(X) {} // definition of A::f void h(int) {} // definition of A::h // A::f, A::g and A::h are now visible at namespace scope // and they are also friends of A::X and A::X::Y }
行內命名空間行內命名空間是指在其原始命名空間定義中使用了選用關鍵字 行內命名空間的成員在許多情況下(列於下方)被視為是封閉命名空間的成員。此特性是遞移的:如果命名空間 N 包含行內命名空間 M,而 M 又包含行內命名空間 O,則 O 的成員可以被使用,如同它們是 M 或 N 的成員一樣。
// in C++14, std::literals and its member namespaces are inline { using namespace std::string_literals; // makes visible operator""s // from std::literals::string_literals auto str = "abc"s; } { using namespace std::literals; // makes visible both // std::literals::string_literals::operator""s // and std::literals::chrono_literals::operator""s auto str = "abc"s; auto min = 60s; } { using std::operator""s; // makes both std::literals::string_literals::operator""s // and std::literals::chrono_literals::operator""s visible auto str = "abc"s; auto min = 60s; } 注意:關於特化(specializations)的規則允許函式庫版本控制:函式庫模板的不同實作可能定義在不同的行內命名空間中,同時仍允許使用者透過主要模板的明確特化來擴展父命名空間。 執行此程式碼 namespace Lib { inline namespace Lib_1 { template<typename T> class A; } template<typename T> void g(T) { /* ... */ } } /* ... */ struct MyClass { /* ... */ }; namespace Lib { template<> class A<MyClass> { /* ... */ }; } int main() { Lib::A<MyClass> a; g(a); // ok, Lib is an associated namespace of A } |
(C++11 起) |
[編輯] 匿名命名空間
匿名命名空間定義 是一種形式為以下的命名空間定義:
inline(選用) namespace 屬性 (選用) { 命名空間主體 } |
|||||||||
inline
|
- | (C++11 起) 如果存在,則使其成為行內命名空間 |
| 屬性 | - | (C++17 起) 任意數量的屬性的選用序列 |
此定義被視為一個具有唯一名稱的命名空間的定義,並且在當前作用域中存在一個指定此匿名命名空間的using-指令(注意:隱式添加的 using-指令使命名空間可供限定名稱查詢和非限定名稱查詢使用,但不能用於引數依賴查詢)。該唯一名稱在整個程式中是唯一的,但在一個翻譯單元(translation unit)內,每個匿名命名空間定義都映射到相同的唯一名稱:同一作用域中的多個匿名命名空間定義表示相同的匿名命名空間。
namespace { int i; // defines ::(unique)::i } void f() { i++; // increments ::(unique)::i } namespace A { namespace { int i; // A::(unique)::i int j; // A::(unique)::j } void g() { i++; } // A::(unique)::i++ } using namespace A; // introduces all names from A into global namespace void h() { i++; // error: ::(unique)::i and ::A::(unique)::i are both in scope A::i++; // ok, increments ::A::(unique)::i j++; // ok, increments ::A::(unique)::j }
|
儘管匿名命名空間中的名稱可能宣告為具有外部連結(external linkage),但由於其命名空間名稱是唯一的,它們從其他翻譯單元中永遠無法存取。 |
(直到 C++11) |
|
匿名命名空間以及直接或間接在匿名命名空間內宣告的所有命名空間都具有內部連結(internal linkage),這意味著在匿名命名空間內宣告的任何名稱都具有內部連結。 |
(C++11 起) |
[編輯] Using-宣告
將在其他地方定義的名稱引入到此 using-宣告所在的宣告區域。
using typename(選用) 巢狀名稱指定符 非限定識別碼 ; |
(直到 C++17) | ||||||||
using 宣告子清單 ; |
(自 C++17 起) | ||||||||
typename 關鍵字
|
- | 當 using-宣告將基底類別中的成員型別引入類別模板時,關鍵字 typename 可根據需要用於解析依賴名稱(dependent names)。 |
| 巢狀名稱指定元 (nested-name-specifier) | - | 名稱和作用域解析運算子 :: 的序列,以作用域解析運算子結尾。單個 :: 指的是全域命名空間。 |
| 非限定識別碼 | - | 一個識別碼表達式(id-expression) |
| 宣告子清單 | - | 由逗號分隔的一個或多個宣告子組成的清單,形式為 typename(選用) 巢狀名稱指定符 非限定識別碼。宣告子之後可以跟隨省略號以指示套件擴展(pack expansion),儘管該形式僅在衍生類別定義中有意義。 |
Using-宣告可以用來將命名空間成員引入其他命名空間和區塊作用域,或者將基底類別成員引入衍生類別定義中,或者將列舉子引入命名空間、區塊和類別作用域中(C++20 起)。
|
具有多個 using-宣告子的 using-宣告等效於相應的單個 using-宣告子組成的 using-宣告序列。 |
(自 C++17 起) |
關於在衍生類別定義中的使用,請參見using 宣告。
由 using-宣告引入到命名空間作用域中的名稱,可以像任何其他名稱一樣使用,包括來自其他作用域的限定查詢。
void f(); namespace A { void g(); } namespace X { using ::f; // global f is now visible as ::X::f using A::g; // A::g is now visible as ::X::g using A::g, A::g; // (C++17) OK: double declaration allowed at namespace scope } void h() { X::f(); // calls ::f X::g(); // calls A::g }
如果在 using-宣告用於從命名空間中取出成員之後,該命名空間被擴展並引入了相同名稱的其他宣告,這些額外的宣告不會透過 using-宣告變得可見(與 using-指令不同)。一個例外是當 using-宣告命名一個類別模板時:後來引入的部分特化(partial specializations)實際上是可見的,因為它們的查詢是透過主要模板進行的。
namespace A { void f(int); } using A::f; // ::f is now a synonym for A::f(int) namespace A // namespace extension { void f(char); // does not change what ::f means } void foo() { f('a'); // calls f(int), even though f(char) exists. } void bar() { using A::f; // this f is a synonym for both A::f(int) and A::f(char) f('a'); // calls f(char) }
Using-宣告不能命名模板識別碼(template-id),或命名空間,或一個帶作用域的列舉子(C++20 前)。using-宣告中的每個宣告子只引入一個名稱,例如,對列舉的 using-宣告不會引入其任何列舉子。
所有關於同名常規宣告、隱藏(hiding)和過載(overloading)規則的限制都適用於 using-宣告。
namespace A { int x; } namespace B { int i; struct g {}; struct x {}; void f(int); void f(double); void g(char); // OK: function name g hides struct g } void func() { int i; using B::i; // error: i declared twice void f(char); using B::f; // OK: f(char), f(int), f(double) are overloads f(3.5); // calls B::f(double) using B::g; g('a'); // calls B::g(char) struct g g1; // declares g1 to have type struct B::g using B::x; using A::x; // OK: hides struct B::x x = 99; // assigns to A::x struct x x1; // declares x1 to have type struct B::x }
如果一個函數是由 using-宣告引入的,那麼宣告一個具有相同名稱和參數列表的函數是格式錯誤的(除非該宣告是針對同一個函數)。如果一個函數模板是由 using-宣告引入的,那麼宣告一個具有相同名稱、參數型別列表、返回型別和模板參數列表的函數模板是格式錯誤的。兩個 using-宣告可以引入具有相同名稱和參數列表的函數,但是如果嘗試呼叫該函數,程式將是格式錯誤的。
namespace B { void f(int); void f(double); } namespace C { void f(int); void f(double); void f(char); } void h() { using B::f; // introduces B::f(int), B::f(double) using C::f; // introduces C::f(int), C::f(double), and C::f(char) f('h'); // calls C::f(char) f(1); // error: B::f(int) or C::f(int)? void f(int); // error: f(int) conflicts with C::f(int) and B::f(int) }
如果一個實體在某個內部命名空間中宣告但未定義,然後透過 using-宣告在外部命名空間中宣告,接著在外部命名空間中出現具有相同非限定名稱的定義,則該定義是外部命名空間的成員並與 using-宣告衝突。
namespace X { namespace M { void g(); // declares, but doesn't define X::M::g() } using M::g; void g(); // Error: attempt to declare X::g which conflicts with X::M::g() }
更一般地,出現在任何命名空間作用域中並使用非限定識別碼引入名稱的宣告,總是將成員引入其所在的命名空間,而不是其他任何命名空間。例外情況是定義在行內命名空間中的主要模板的明確實例化和明確特化:因為它們不引入新名稱,所以它們可以在封閉命名空間中使用非限定識別碼。
[編輯] Using-指令
一個using-指令是一種區塊宣告,具有以下語法:
屬性 (選用) using namespace 巢狀名稱指定符 (選用) 命名空間名稱 ; |
(1) | ||||||||
| 屬性 | - | (C++11 起) 適用於此 using-指令的任意數量的屬性 |
| 巢狀名稱指定元 (nested-name-specifier) | - | 名稱和作用域解析運算子 :: 的序列,以作用域解析運算子結尾。單個 :: 指的是全域命名空間。在查詢此序列中的名稱時,查詢僅考慮命名空間宣告。 |
| 命名空間名稱 | - | 一個命名空間的名稱。在查詢此名稱時,查詢僅考慮命名空間宣告。 |
Using-指令只允許出現在命名空間作用域和區塊作用域中。從 using-指令之後的任何名稱的非限定名稱查詢的角度來看,直到其所在作用域的結束,命名空間名稱 中的每個名稱都可見,如同它被宣告在同時包含該 using-指令和 命名空間名稱 的最鄰近封閉命名空間中。
Using-指令不會將任何名稱添加到其所在的宣告區域中(與 using-宣告不同),因此不會阻止宣告相同的名稱。
Using-指令對於非限定查詢是遞移的:如果一個作用域包含一個指定 命名空間名稱 的 using-指令,而該命名空間本身又包含另一個 命名空間名稱-2 的 using-指令,其效果就如同第二個命名空間的 using-指令出現在第一個命名空間中一樣。這些遞移命名空間出現的順序不會影響名稱查詢。
namespace A { int i; } namespace B { int i; int j; namespace C { namespace D { using namespace A; // Names from A are "injected" into D. // Unqualified lookup within D considers these names to have the same // scope as the global scope (e.g. for the purposes of name hiding). // Qualified lookup referring to D (D::name for some name) // will find the same name as unqualified lookup within D. int j; int k; int a = i; // i is B::i, because A::i is hidden by B::i int b = ::i; // error: there is still no i in the global namespace } using namespace D; // names from D and A are injected into C int k = 89; // OK to declare name identical to one introduced by a using int l = k; // ambiguous: C::k or D::k int m = i; // ok: B::i hides A::i int n = j; // ok: D::j hides B::j } } // These are all equivalent definitions: int t0 = B::i; int t1 = B::C::a; int t2 = B::C::D::a;
如果在 using-指令用於指定某個命名空間之後,該命名空間被擴展並添加了額外的成員和/或 using-指令,這些額外的成員和額外的命名空間將透過 using-指令可見(與 using-宣告不同)。
namespace D { int d1; void f(char); } using namespace D; // introduces D::d1, D::f, D::d2, D::f, // E::e, and E::f into global namespace! int d1; // OK: no conflict with D::d1 when declaring namespace E { int e; void f(int); } namespace D // namespace extension { int d2; using namespace E; // transitive using-directive void f(int); } void f() { d1++; // error: ambiguous ::d1 or D::d1? ::d1++; // OK D::d1++; // OK d2++; // OK, d2 is D::d2 e++; // OK: e is E::e due to transitive using f(1); // error: ambiguous: D::f(int) or E::f(int)? f('a'); // OK: the only f(char) is D::f(char) }
[編輯] 備註
在任何命名空間作用域中的 using-指令 using namespace std; 會將命名空間 std 中的每個名稱引入全域命名空間(因為全域命名空間是同時包含 std 和任何使用者宣告命名空間的最鄰近命名空間),這可能導致不希望的名稱衝突。這以及其他 using-指令通常被認為是在標頭檔的全域作用域中的不良實踐(SF.7: 不要在標頭檔的全域作用域中寫 using namespace)。
| 特性測試巨集 | 數值 | 標準 | 功能 |
|---|---|---|---|
__cpp_namespace_attributes |
201411L |
(C++17) | 命名空間的屬性 |
[編輯] 關鍵字
[編輯] 範例
此範例展示如何使用命名空間來建立一個已在 std 命名空間中命名的類別。
#include <vector> namespace vec { template<typename T> class vector { // ... }; } // of vec int main() { std::vector<int> v1; // Standard vector. vec::vector<int> v2; // User defined vector. // v1 = v2; // Error: v1 and v2 are different object's type. { using namespace std; vector<int> v3; // Same as std::vector v1 = v3; // OK } { using vec::vector; vector<int> v4; // Same as vec::vector v2 = v4; // OK } }
[編輯] 缺陷報告
下列更改行為的缺陷報告追溯應用於之前的 C++ 標準。
| DR | 應用於 | 出版時的行為 | 正確的行為 |
|---|---|---|---|
| CWG 101 | C++98 | 如果命名空間 作用域或區塊作用域中的函數宣告,以及一個由 using-宣告引入的函數,宣告了同一個函數(無歧義),則程式是格式錯誤的 |
已允許 |
| CWG 373 | C++98 | 查詢僅考慮命名空間宣告,僅針對 using-指令運算元中的最後一個名稱(這 不理想,因為類別不能包含命名空間) |
查詢限制 適用於 using-指令運算元中的所有名稱 |
| CWG 460 | C++98 | using-宣告可以命名一個命名空間 | 已禁止 |
| CWG 565 | C++98 | using-宣告不能引入一個函數 與同一作用域中的另一個函數相同,但 該限制不適用於函數模板 |
應用相同的限制 也適用於函數模板 |
| CWG 986 | C++98 | using-指令對於限定查詢是遞移的 | 僅對非限定查詢遞移 |
| CWG 987 | C++98 | 在巢狀命名空間中宣告的實體 也是封閉命名空間的成員 |
巢狀作用域已排除 |
| CWG 1021 | C++98 | 不清楚一個實體的定義 透過 using-宣告引入命名空間後 是否被視為在該命名空間中定義 |
不在該命名空間中定義 |
| CWG 1838 | C++98 | 外部命名空間中的非限定定義 可以定義一個已宣告但未定義的實體 在另一個命名空間中,並透過 using 引入 |
非限定定義 總是參照到 它所在的命名空間 |
| CWG 2155 | C++98 | CWG 問題 1838 的解決方案並未 應用於類別和列舉宣告 |
已應用 |
[編輯] 另請參閱
| 命名空間別名 | 建立現有命名空間的別名 |