命名空间
命名空间提供了一种方法,用于防止大型项目中的名称冲突。
在命名空间块内声明的实体被放置在命名空间作用域中,这可以防止它们与其他作用域中同名的实体混淆。
在所有命名空间块之外声明的实体属于*全局命名空间*。全局命名空间属于全局作用域,并且可以使用前导::
显式引用。尽管它没有声明,但全局命名空间不是一个无名命名空间。
允许多个同名命名空间块。这些块内的所有声明都在相同的命名空间作用域中声明。
目录 |
[编辑] 语法
namespace ns-name { declarations } |
(1) | ||||||||
inline namespace ns-name { declarations } |
(2) | (C++11 起) | |||||||
namespace { declarations } |
(3) | ||||||||
ns-name :: member-name |
(4) | ||||||||
using namespace ns-name ; |
(5) | ||||||||
using ns-name :: member-name ; |
(6) | ||||||||
namespace name = qualified-namespace ; |
(7) | ||||||||
namespace ns-name :: member-name { declarations } |
(8) | (C++17 起) | |||||||
namespace ns-name :: inline member-name { declarations } |
(9) | (C++20 起) | |||||||
[编辑] 解释
[编辑] 命名空间
inline (可选) namespace attr (可选) identifier { namespace-body } |
|||||||||
inline
|
- | (自 C++11 起) 如果存在,使其成为一个内联命名空间(见下文)。不能出现在 *extension-namespace-definition* 中,如果 *original-namespace-definition* 没有使用 inline 。 | ||
属性 | - | (自 C++17 起) 任意数量的属性的可选序列。 | ||
标识符 | - | 要么是
| ||
命名空间体 | - | 可能为空的任意类型的声明序列(包括类和函数定义以及嵌套命名空间)。 |
命名空间定义只允许在命名空间作用域内,包括全局作用域。
要重新打开一个现有命名空间(正式地,成为一个 *extension-namespace-definition*),命名空间定义中使用的 identifier 的查找必须解析为命名空间名称(而不是命名空间别名),该名称被声明为外围命名空间的成员或外围命名空间内的内联命名空间的成员。
namespace-body 定义了一个命名空间作用域,它影响名称查找。
出现在 namespace-body 内的声明(包括嵌套命名空间定义)引入的所有名称都成为命名空间 identifier 的成员,无论此命名空间定义是原始命名空间定义(引入 identifier),还是扩展命名空间定义(“重新打开”已定义的命名空间)。
在命名空间主体内声明的命名空间成员可以通过显式限定在外部定义或重新声明。
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 {} }
命名空间外的定义和重新声明只允许在
- 声明点之后,
- 在命名空间作用域中,以及
- 在包含原始命名空间的命名空间中(包括全局命名空间)。
此外,它们必须使用限定 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 中的友元声明引入的名称成为 X 的最内层包含命名空间的成员,但它们不会对普通名称查找(无论是非限定还是限定)可见,除非在命名空间作用域中提供了匹配的声明,无论是在类定义之前还是之后。此类名称可以通过ADL找到,ADL 考虑命名空间和类。
在决定名称是否与先前声明的名称冲突时,友元声明只考虑最内层包含命名空间。
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; } 注意:关于特化规则允许库版本化:库模板的不同实现可以在不同的内联命名空间中定义,同时仍然允许用户通过主模板的显式特化来扩展父命名空间。 运行此代码 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 起) |
[编辑] 无名命名空间
*unnamed-namespace-definition* 是一种以下形式的命名空间定义:
inline (可选) namespace attr (可选) { namespace-body } |
|||||||||
inline
|
- | (自 C++11 起) 如果存在,使其成为一个内联命名空间。 |
属性 | - | (自 C++17 起) 任意数量的属性的可选序列。 |
此定义被视为具有唯一名称的命名空间的定义,并在当前作用域中有一个提名此无名命名空间的 *using-directive*(注意:隐式添加的 using-directive 使命名空间可用于限定名查找和非限定名查找,但不能用于实参依赖查找)。该唯一名称在整个程序中是唯一的,但在一个翻译单元内,每个无名命名空间定义都映射到相同的唯一名称:同一作用域中的多个无名命名空间定义表示相同的无名命名空间。
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 }
即使无名命名空间中的名称可能被声明为具有外部链接,但由于其命名空间名称是唯一的,它们永远无法从其他翻译单元访问。 |
(C++11 前) |
无名命名空间以及直接或间接在无名命名空间内声明的所有命名空间都具有内部链接,这意味着在无名命名空间内声明的任何名称都具有内部链接。 |
(C++11 起) |
[编辑] using-声明
将其他地方定义的名称引入到此 using-声明出现的声明区域。
using typename (可选) nested-name-specifier unqualified-id ; |
(C++17 前) | ||||||||
using declarator-list ; |
(C++17 起) | ||||||||
类型名
|
- | 当 using-declaration 将基类的成员类型引入类模板时,关键字 typename 可用于解析依赖名称。 |
嵌套名称限定符 | - | 名称序列和作用域解析运算符:: ,以作用域解析运算符结尾。单个:: 指代全局命名空间。 |
非限定ID | - | 一个ID 表达式 |
声明符列表 | - | 一个或多个声明符的逗号分隔列表,形式为 typename (可选) nested-name-specifier unqualified-id。声明符后可以跟省略号以指示包扩展,尽管该形式仅在派生类定义中才有意义。 |
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-声明命名一个类模板时:稍后引入的部分特化实际上是可见的,因为它们的查找是通过主模板进行的。
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-声明不能命名模板-id或命名空间,或有作用域的枚举器(直至 C++20)。using-声明中的每个声明符只引入一个名称,例如,枚举的 using-声明不引入其任何枚举器。
对同名实体常规声明的所有限制、隐藏和重载规则均适用于 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() }
更一般地,出现在任何命名空间作用域中并使用非限定标识符引入名称的声明总是将成员引入其所在的命名空间,而不是其他任何命名空间。例外情况是内联命名空间中定义的主模板的显式实例化和显式特化:因为它们不引入新名称,所以它们可以在外围命名空间中使用非限定 ID。
[编辑] using-指令
*using-directive* 是一个具有以下语法的块声明:
attr (可选) using namespace nested-name-specifier (可选) namespace-name ; |
(1) | ||||||||
属性 | - | (自 C++11 起) 适用于此 using-directive 的任意数量的属性。 |
嵌套名称限定符 | - | 名称序列和作用域解析运算符:: ,以作用域解析运算符结尾。单个:: 指代全局命名空间。在查找此序列中的名称时,查找只考虑命名空间声明。 |
命名空间名称 | - | 命名空间的名称。在查找此名称时,查找只考虑命名空间声明。 |
using-指令只允许在命名空间作用域和块作用域中。从using-指令之后到其出现的作用域结束的任何名称的非限定名称查找的角度来看,namespace-name 中的每个名称都可见,就像它在包含 using-directive 和 namespace-name 的最近外围命名空间中声明一样。
Using-directive 不会向其出现的声明区域添加任何名称(与 using-declaration 不同),因此不会阻止声明相同的名称。
Using-指令对于非限定查找而言是传递的:如果一个作用域包含一个提名 namespace-name 的 using-指令,而 namespace-name 本身包含针对某个 namespace-name-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-directive 被用来提名某个命名空间后,该命名空间被扩展并添加了额外的成员和/或 using-directive,则这些额外的成员和额外的命名空间通过 using-directive 可见(与 using-declaration 不同)。
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-directive using namespace std; 会将命名空间 std
中的每个名称引入全局命名空间(因为全局命名空间是包含 std
和任何用户声明命名空间的最近命名空间),这可能导致不必要的名称冲突。在头文件的文件作用域中使用此和其他 using-directive 通常被认为是糟糕的实践(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++ 标准。
缺陷报告 | 应用于 | 发布时的行为 | 正确的行为 |
---|---|---|---|
CWG 101 | C++98 | 如果命名空间作用域或块作用域中的函数声明和通过 using-声明引入的函数声明了相同的函数(无歧义),则程序格式错误。 scope or block scope and a function introduced by a using-declaration declare the same function (no ambiguity) |
允许 |
CWG 373 | C++98 | 查找只考虑 using-指令操作数中最后一个名称的命名空间声明(这是次优的,因为类不能包含命名空间) the last name in the operand of a using-directive (which is sub-optimal, because classes cannot contain namespaces) |
查找限制 适用于所有名称 using-指令的操作数 |
CWG 460 | C++98 | using-声明可以命名一个命名空间 | 已禁止 |
CWG 565 | C++98 | using-声明不能引入与同一作用域中另一个函数相同的函数,但此限制不适用于函数模板。 identical to another function in the same scope, but the restriction was not applied to function templates |
应用相同的限制 也适用于函数模板 |
CWG 986 | C++98 | using-指令对限定查找具有传递性 | 只对非限定查找具有传递性 |
CWG 987 | C++98 | 嵌套命名空间中声明的实体 也是外围命名空间的成员 |
嵌套作用域排除在外 |
CWG 1021 | C++98 | 不清楚通过 using-声明引入命名空间的实体定义是否被认为是定义在该命名空间中 is introduced to a namespace via using-declaration is considered to be defined in that namespace |
未在该命名空间中定义 |
CWG 1838 | C++98 | 外部命名空间中的非限定定义 可以定义在另一个命名空间中声明但未定义的实体,并通过 using 引入。 another namespace and pulled in by a using |
非限定定义 总是指 其命名空间 |
CWG 2155 | C++98 | CWG issue 1838 的解决方案未应用于类和枚举声明。 applied to class and enumeration declarations |
已应用 |
[编辑] 另请参阅
命名空间别名 | 创建现有命名空间的别名 |