命名空间
命名空间为大型项目中的名称冲突提供了一种预防方法。
在命名空间块内声明的实体被放置在命名空间作用域中,这防止了它们与其他作用域中同名实体混淆。
在所有命名空间块之外声明的实体属于全局命名空间。全局命名空间属于全局作用域,并且可以使用前导 ::
显式引用。虽然它没有声明,但全局命名空间不是未命名命名空间。
允许使用相同名称的多个命名空间块。这些块内的所有声明都在相同的命名空间作用域中声明。
内容 |
[编辑] 语法
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 起) 如果存在,则使其成为内联命名空间(见下文)。如果 original-namespace-definition 未使用 inline ,则不能出现在 extension-namespace-definition 上 | ||
attr | - | (自 C++17 起) 任意数量属性的可选序列 | ||
identifier | - | 以下之一
| ||
namespace-body | - | 任何类型声明(包括类和函数定义以及嵌套命名空间)的可能为空的序列 |
命名空间定义仅允许在命名空间作用域(包括全局作用域)中。
要重新打开现有命名空间(正式地,成为 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 {} }
命名空间外的定义和重新声明仅在以下情况下允许
- 声明点之后,
- 在命名空间作用域中,以及
- 在封闭原始命名空间的命名空间中(包括全局命名空间)。
此外,它们必须使用 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 中的友元声明引入的名称成为 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 }
内联命名空间内联命名空间是在其 original-namespace-definition 中使用可选关键字 在许多情况下(如下所列),内联命名空间的成员被视为好像它们是封闭命名空间的成员。此属性是传递性的:如果命名空间 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 起) 如果存在,则使其成为内联命名空间 |
attr | - | (自 C++17 起) 任意数量属性的可选序列 |
此定义被视为具有唯一名称的命名空间的定义,以及当前作用域中指定此未命名命名空间的 using-directive(注意:隐式添加的 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 (可选) nested-name-specifier unqualified-id ; |
(直到 C++17) | ||||||||
using declarator-list ; |
(自 C++17 起) | ||||||||
typename
|
- | 关键字 typename 可以根据需要用于解析依赖名称,当 using-声明将基类中的成员类型引入类模板时 |
nested-name-specifier | - | 名称和作用域解析运算符 :: 的序列,以作用域解析运算符结尾。单个 :: 指的是全局命名空间。 |
unqualified-id | - | id-表达式 |
declarator-list | - | 一个或多个声明符的逗号分隔列表,形式为 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() }
更一般地,出现在任何命名空间作用域中并使用非限定标识符引入名称的声明始终将成员引入到它所在的命名空间中,而不是任何其他命名空间。例外情况是在内联命名空间中定义的主模板的显式实例化和显式特化:因为它们不引入新名称,所以它们可以在封闭命名空间中使用 unqualified-id。
[编辑] Using-指令
using-directive 是具有以下语法的块声明
attr (可选) using namespace nested-name-specifier (可选) namespace-name ; |
(1) | ||||||||
attr | - | (自 C++11 起) 应用于此 using-指令的任意数量的属性 |
nested-name-specifier | - | 名称和作用域解析运算符 :: 的序列,以作用域解析运算符结尾。单个 :: 指的是全局命名空间。当查找此序列中的名称时,查找仅考虑命名空间声明 |
namespace-name | - | 命名空间的名称。当查找此名称时,查找仅考虑命名空间声明 |
Using-指令仅允许在命名空间作用域和块作用域中使用。从非限定名称查找的角度来看,在 using-指令之后的任何名称,直到它出现的作用域结束,namespace-name 中的每个名称都是可见的,就好像它是在最近的封闭命名空间中声明的一样,该命名空间同时包含 using-指令和 namespace-name。
Using-指令不会将任何名称添加到它出现的声明区域(与 using-声明不同),因此不会阻止声明相同的名称。
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-指令指定某些命名空间后,命名空间被扩展并且向其中添加了其他成员和/或 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)。
特性测试宏 | 值 | Std | 特性 |
---|---|---|---|
__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 issue 1838 的解决方案未 应用于类和枚举声明 |
应用 |
[编辑] 另请参阅
命名空间别名 | 创建现有命名空间的别名 |