命名空间
命名空间提供了一种在大型项目中防止名称冲突的方法。
在命名空间块内声明的实体被放置在命名空间作用域中,这可以防止它们被误认为是其他作用域中同名的实体。
在所有命名空间块之外声明的实体属于全局命名空间。全局命名空间属于全局作用域,并且可以通过在前面加上 ::
来显式引用。虽然它没有声明,但全局命名空间不是未命名命名空间。
允许多个同名的命名空间块。这些块中的所有声明都在同一个命名空间作用域中声明。
目录 |
[编辑] 语法
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 {} }
命名空间外的定义和重新声明只允许在以下情况下进行:
- 声明点之后,
- 在命名空间作用域内,以及
- 在封闭原始命名空间的命名空间内(包括全局命名空间)。
此外,它们必须使用限定标识符语法。
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 起) |
[编辑] 未命名命名空间
未命名命名空间定义 是以下形式的命名空间定义:
inline (可选) namespace attr (可选) { 命名空间体 } |
|||||||||
inline
|
- | (自 C++11 起) 如果存在,则使其成为内联命名空间。 |
特性 | - | (自 C++17 起) 可选的任意数量的特性序列 |
此定义被视为具有唯一名称的命名空间的定义,以及当前作用域中的 using 指令,该指令指定此未命名命名空间(注意:隐式添加的 using 指令使命名空间可用于 限定名称查找 和 非限定名称查找,但不用于 依赖于参数的查找)。唯一名称在整个程序中是唯一的,但在一个翻译单元中,每个未命名命名空间定义都映射到相同的唯一名称:同一作用域中的多个未命名命名空间定义表示相同的未命名命名空间。
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 (可选) 嵌套名称说明符 非限定标识符 ; |
(直到 C++17) | ||||||||
using 声明符列表 ; |
(自 C++17 起) | ||||||||
typename
|
- | 当 using 声明将基类的成员类型引入类模板时,可以使用关键字 typename 来解析 依赖名称。 |
嵌套名称说明符 | - | 一系列名称和作用域解析运算符 :: ,以作用域解析运算符结尾。单个 :: 指的是全局命名空间。 |
非限定标识符 | - | 一个 标识符表达式 |
声明符列表 | - | 以逗号分隔的一个或多个声明符列表,其形式为 typename (可选) 嵌套名称说明符 非限定标识符。声明符后面可以跟一个省略号,表示 参数包扩展,尽管这种形式只在 派生类定义 中有意义。 |
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-declaration 从命名空间中获取成员之后,命名空间被扩展并且引入了相同名称的附加声明,则这些附加声明不会通过 using-declaration 可见(与 using-directive 相反)。一个例外是当 using-declaration 命名类模板时:稍后引入的部分特化实际上是可见的,因为它们的 查找 通过主模板进行。
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-declaration 不能命名 模板 ID 或命名空间或作用域枚举器(直到 C++20)。using-declaration 中的每个声明符只引入一个名称,例如枚举的 using-declaration 不引入其任何枚举器。
对相同名称的常规声明的所有限制、隐藏和重载规则都适用于 using-declaration。
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-declaration 引入的,则声明具有相同名称和参数列表的函数是非法的(除非该声明是针对同一函数)。如果函数模板是由 using-declaration 引入的,则声明具有相同名称、参数类型列表、返回类型和模板参数列表的函数模板是非法的。两个 using-declaration 可以引入具有相同名称和参数列表的函数,但如果尝试调用该函数,则程序是非法的。
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-declaration 声明,然后外部命名空间中出现具有相同非限定名称的定义,则该定义是外部命名空间的成员并与 using-declaration 冲突。
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-directives
using-directive 是具有以下语法的 块声明
attr (可选) using namespace 嵌套名称说明符 (可选) 命名空间名称 ; |
(1) | ||||||||
特性 | - | (自 C++11 起) 适用于此 using-directive 的任意数量的 属性 |
嵌套名称说明符 | - | 以作用域解析运算符结尾的名称和作用域解析运算符 :: 序列。单个 :: 指的是全局命名空间。在查找此序列中的名称时,查找 仅考虑命名空间声明。 |
命名空间名称 | - | 命名空间的名称。在查找此名称时,查找 仅考虑命名空间声明。 |
Using-directive 仅允许在命名空间 作用域 和块作用域中使用。从 using-directive 之后到其出现的作用域结束的任何名称的 非限定名称查找 的角度来看,命名空间名称 中的每个名称都可见,就好像它是在包含 using-directive 和 命名空间名称 的最近封闭命名空间中声明的一样。
Using-directive 不会向其出现的声明区域添加任何名称(与 using-declaration 不同),因此不会阻止声明相同的名称。
出于 非限定名称查找 的目的,Using-directive 是可传递的:如果作用域包含一个指定 命名空间名称 的 using-directive,而该命名空间本身包含针对某个 命名空间名称 2 的 using-directive,则效果就好像第二个命名空间中的 using-directive 出现在第一个命名空间中一样。这些传递命名空间出现的顺序不会影响名称查找。
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++ 标准。
DR | 应用于 | 发布时的行为 | 正确的行为 |
---|---|---|---|
CWG 101 | C++98 | 如果命名空间作用域或块作用域中的函数声明和 using-declaration 引入的函数声明相同的函数(没有歧义),则程序是非法的。 |
允许 |
CWG 373 | C++98 | 查找仅考虑 using-directive 的操作数中最后一个名称的命名空间声明(这并非最佳,因为类不能包含命名空间)。 |
查找限制 适用于 using-directive 的操作数中的所有名称。 |
CWG 460 | C++98 | using-declaration 可以命名命名空间。 | 禁止 |
CWG 565 | C++98 | using-declaration 不能引入与同一作用域中的另一个函数相同的函数,但此限制不适用于函数模板。 |
对函数模板应用与函数相同的限制。 |
CWG 986 | C++98 | using-directive 对于限定查找是可传递的。 | 仅对非限定查找可传递。 |
CWG 987 | C++98 | 嵌套命名空间中声明的实体也是封闭命名空间的成员。 |
排除嵌套作用域 |
CWG 1021 | C++98 | 尚不清楚通过 using-declaration 引入命名空间的实体的定义是否被认为是在该命名空间中定义的。 |
未在该命名空间中定义 |
CWG 1838 | C++98 | 外部命名空间中的非限定定义可以定义在另一个命名空间中声明但未定义并通过 using 拉入的实体。 |
非限定定义 始终指的是 其命名空间 |
CWG 2155 | C++98 | CWG 问题 1838 的决议未 应用于类和枚举声明 |
应用 |
[编辑] 参见
命名空间别名 | 创建现有命名空间的别名 |