Namespaces
命名空间提供了一种在大型项目中防止命名冲突的方法。
在命名空间块内部声明的实体会被置于命名空间作用域内,这可以防止它们与其他作用域中同名的实体产生混淆。
在所有命名空间块外部声明的实体属于
全局命名空间
。全局命名空间属于
全局作用域
,可通过前导的
::
显式引用。虽然全局命名空间没有声明,但它并非
无名命名空间
。
允许存在多个同名的命名空间块。这些块内的所有声明都位于同一个命名空间作用域内。
目录 |
语法
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
|
- |
(since C++11)
若存在,则将此命名空间设为内联命名空间(见下文)。若
原始命名空间定义
未使用
inline
,则不能在
扩展命名空间定义
中出现
|
||
| attr | - | (since C++17) 可选的任意数量 属性 序列 | ||
| identifier | - |
可以是
|
||
| namespace-body | - | 可能为空的任意类型 声明 序列(包括类和函数定义以及嵌套命名空间) |
命名空间定义仅允许在命名空间作用域内进行,包括全局作用域。
要重新打开一个已存在的命名空间(正式来说,成为 扩展命名空间定义 ),在命名空间定义中使用的 标识符 的查找必须解析为一个命名空间名称(而非命名空间别名),该名称必须被声明为外围命名空间或外围命名空间内联命名空间的成员。
namespace-body 定义了一个 命名空间作用域 ,该作用域会影响 名称查找 。
所有出现在 namespace-body 中的声明所引入的名称(包括嵌套命名空间定义),都会成为命名空间 identifier 的成员,无论该命名空间定义是原始命名空间定义(首次引入 identifier ),还是扩展命名空间定义(“重新打开”已定义的命名空间)
在命名空间体内声明的命名空间成员,可以通过显式限定在外部进行定义或重新声明
namespace Q { namespace V // V 是 Q 的成员,并在 Q 内完整定义 { // namespace Q::V { // C++17 对上述代码的替代写法 class C { void m(); }; // C 是 V 的成员,并在 V 内完整定义 // C::m 仅作声明 void f(); // f 是 V 的成员,但仅在此声明 } void V::f() // 在 V 外部定义 V 的成员 f // f 的封闭命名空间仍是全局命名空间、Q 和 Q::V { extern void h(); // 此处声明 ::Q::V::h } void V::C::m() // 在命名空间(及类体)外部定义 V::C::m // 封闭命名空间为全局命名空间、Q 和 Q::V {} }
命名空间外的定义和重声明仅允许
- 在声明点之后,
- 在命名空间作用域中,以及
- 在包含原始命名空间的封闭命名空间(包括全局命名空间)中。
此外,它们必须使用限定标识符语法。
namespace Q { namespace V // V 的原始命名空间定义 { void f(); // Q::V::f 的声明 } void V::f() {} // 正确 void V::g() {} // 错误:g() 尚未成为 V 的成员 namespace V // V 的扩展命名空间定义 { void g(); // Q::V::g 的声明 } } namespace R // 不是 Q 的封闭命名空间 { void Q::V::g() {} // 错误:不能在 R 内部定义 Q::V::g } void Q::V::g() {} // 正确:全局命名空间包含 Q
在非局部类 X 中的 friend 声明所引入的名称,会成为 X 的最内层外围命名空间的成员,但这些名称对常规的 名称查找 (无论是 无限定查找 还是 有限定查找 )均不可见,除非在命名空间作用域内(类定义之前或之后)提供了匹配的声明。此类名称可以通过同时考虑命名空间和类的 ADL 被找到。
在判断名称是否与先前声明的名称冲突时,此类友元声明仅考虑最内层封闭命名空间。
void h(int); namespace A { class X { friend void f(X); // A::f 是友元函数 class Y { friend void g(); // A::g 是友元函数 friend void h(int); // A::h 是友元函数,与 ::h 无冲突 }; }; // A::f、A::g 和 A::h 在命名空间作用域不可见 // 尽管它们是命名空间 A 的成员 X x; void g() // A::g 的定义 { f(x); // 通过 ADL 查找到 A::X::f } void f(X) {} // A::f 的定义 void h(int) {} // A::h 的定义 // A::f、A::g 和 A::h 现在在命名空间作用域可见 // 并且它们也是 A::X 和 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
|
- | (since C++11) 若存在,则使此命名空间成为内联命名空间 |
| attr | - | (since C++17) 可选的任意数量 属性 序列 |
该定义被视为一个具有唯一名称的命名空间定义,并在当前作用域内隐式添加一条 using 指令 以指名该无名命名空间(注意:隐式添加的 using 指令使得该命名空间可用于 限定名称查找 和 非限定名称查找 ,但不适用于 实参依赖查找 )。 该唯一名称在整个程序中保持唯一,但在同一翻译单元内,所有无名命名空间定义都映射到相同的唯一名称:同一作用域中的多个无名命名空间定义表示同一个无名命名空间。
namespace { int i; // 定义 ::(unique)::i } void f() { i++; // 递增 ::(unique)::i } namespace A { namespace { int i; // A::(unique)::i int j; // A::(unique)::j } void g() { i++; } // A::(unique)::i++ } using namespace A; // 将A中所有名称引入全局命名空间 void h() { i++; // 错误:::(unique)::i 与 ::A::(unique)::i 同时存在于作用域中 A::i++; // 正确:递增 ::A::(unique)::i j++; // 正确:递增 ::A::(unique)::j }
|
尽管无名命名空间中的名称可能被声明为外部链接,但由于其命名空间名称是唯一的,它们永远无法从其他翻译单元访问。 |
(C++11 前) |
|
无名命名空间以及直接或间接在无名命名空间内声明的所有命名空间都具有 内部链接 ,这意味着在无名命名空间内声明的任何名称都具有内部链接。 |
(C++11 起) |
using 声明
将别处定义的名称引入当前使用声明所在的声明区域。
using
typename
(可选)
嵌套名称说明符
非限定标识符
;
|
(C++17 前) | ||||||||
using
声明符列表
;
|
(C++17 起) | ||||||||
typename
|
- |
当 using 声明从基类向类模板引入成员类型时,可使用
typename
关键字解析
依赖名
|
| nested-name-specifier | - |
以作用域解析运算符
::
结尾的名称和作用域解析运算符序列。单个
::
表示全局命名空间
|
| unqualified-id | - | 一个 标识表达式 |
| declarator-list | - |
以逗号分隔的声明符列表,形式为
typename
(可选)
nested-name-specifier
unqualified-id
。声明符后可接省略号表示
包展开
,但该形式仅在
派生类定义
中有意义
|
using 声明可用于将命名空间成员引入其他命名空间和块作用域,或将基类成员引入派生类定义 ,或将 枚举项 引入命名空间、块及类作用域 (C++20 起) 。
|
具有多个 using 声明符的 using 声明等价于对应的单 using 声明符序列的 using 声明。 |
(since C++17) |
关于在派生类定义中的使用,请参见 using declaration 。
通过 using 声明引入命名空间作用域的名称可以像其他名称一样使用,包括从其他作用域进行限定查找:
void f(); namespace A { void g(); } namespace X { using ::f; // 全局函数 f 现在可作为 ::X::f 访问 using A::g; // A::g 现在可作为 ::X::g 访问 using A::g, A::g; // (C++17) 允许在命名空间作用域内重复声明 } void h() { X::f(); // 调用 ::f X::g(); // 调用 A::g }
如果在使用 using 声明从命名空间获取成员后,该命名空间被扩展并引入了相同名称的额外声明,这些额外声明不会通过 using 声明变为可见(与 using 指令相反)。一个例外是当 using 声明指定类模板时:后续引入的部分特化实际上是可见的,因为它们的 查找 是通过主模板进行的。
namespace A { void f(int); } using A::f; // ::f 现在成为 A::f(int) 的同义词 namespace A // 命名空间扩展 { void f(char); // 不会改变 ::f 的含义 } void foo() { f('a'); // 调用 f(int),即使存在 f(char) } void bar() { using A::f; // 此处的 f 同时是 A::f(int) 和 A::f(char) 的同义词 f('a'); // 调用 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); // 正确:函数名 g 隐藏了结构体 g } void func() { int i; using B::i; // 错误:i 被重复声明 void f(char); using B::f; // 正确:f(char)、f(int)、f(double) 构成重载 f(3.5); // 调用 B::f(double) using B::g; g('a'); // 调用 B::g(char) struct g g1; // 声明 g1 的类型为 struct B::g using B::x; using A::x; // 正确:隐藏了 struct B::x x = 99; // 赋值给 A::x struct x x1; // 声明 x1 的类型为 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; // 引入 B::f(int) 和 B::f(double) using C::f; // 引入 C::f(int)、C::f(double) 和 C::f(char) f('h'); // 调用 C::f(char) f(1); // 错误:B::f(int) 还是 C::f(int)? void f(int); // 错误:f(int) 与 C::f(int) 和 B::f(int) 冲突 }
如果某个实体在某个内部命名空间中被声明但未定义,然后通过 using 声明在外层命名空间中声明,接着在外层命名空间中出现具有相同非限定名称的定义,则该定义属于外层命名空间的成员,并与 using 声明产生冲突:
namespace X { namespace M { void g(); // 声明但未定义 X::M::g() } using M::g; void g(); // 错误:尝试声明与 X::M::g() 冲突的 X::g }
更一般地说,任何出现在命名空间作用域中并使用非限定标识符引入名称的声明,始终会将其作为成员引入到它所在的命名空间,而不会引入到任何其他命名空间。例外情况是定义在内联命名空间中的主模板的显式实例化和显式特化:由于它们不引入新名称,因此可以在外围命名空间中使用非限定标识符。
Using 指令
一个 using 指令 是具有以下语法的 块声明 :
attr
(可选)
using
namespace
nested-name-specifier
(可选)
namespace-name
;
|
(1) | ||||||||
| attr | - | (since C++11) 任意数量的适用于此 using 指令的 属性 |
| nested-name-specifier | - |
以作用域解析运算符结尾的名称和作用域解析运算符
::
序列。单个
::
表示全局命名空间。在查找此序列中的名称时,
查找
仅考虑命名空间声明
|
| namespace-name | - | 命名空间的名称。在查找此名称时, 查找 仅考虑命名空间声明 |
using 指令仅允许在命名空间 作用域 和块作用域中使用。从 using 指令之后到其所在作用域结束为止,对于任何名称的 非限定名称查找 而言,来自 命名空间名 的每个名称都可见,如同它被声明在同时包含该 using 指令和 命名空间名 的最近外层命名空间中。
using 指令不会向其所出现的声明区域添加任何名称(与 using 声明不同),因此不会阻止同名声明。
using 指令在 非限定查找 中具有传递性:若某作用域包含指名 namespace-name 的 using 指令,而该命名空间自身又包含针对某个 namespace-name-2 的 using 指令,则效果等同于第二个命名空间中的 using 指令出现在第一个命名空间中。这些传递性命名空间的出现顺序不会影响名称查找。
namespace A { int i; } namespace B { int i; int j; namespace C { namespace D { using namespace A; // A中的名称被"注入"到D中 // D内的非限定查找会将这些名称视为与全局作用域具有相同层级(例如用于名称隐藏的目的) // 引用D的限定查找(对某个名称使用D::name)将找到与D内非限定查找相同的名称 int j; int k; int a = i; // i是B::i,因为A::i被B::i隐藏 int b = ::i; // 错误:全局命名空间中仍不存在i } using namespace D; // D和A中的名称被注入到C中 int k = 89; // 允许声明与using引入的名称相同的标识符 int l = k; // 二义性:C::k 还是 D::k int m = i; // 正确:B::i隐藏了A::i int n = j; // 正确:D::j隐藏了B::j } } // 以下均为等效定义: 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; // 将 D::d1、D::f、D::d2、D::f、 // E::e 和 E::f 引入全局命名空间! int d1; // 正确:声明时不会与 D::d1 冲突 namespace E { int e; void f(int); } namespace D // 命名空间扩展 { int d2; using namespace E; // 传递性 using 指令 void f(int); } void f() { d1++; // 错误:存在歧义:::d1 还是 D::d1? ::d1++; // 正确 D::d1++; // 正确 d2++; // 正确,d2 是 D::d2 e++; // 正确:由于传递性 using,e 是 E::e f(1); // 错误:存在歧义:D::f(int) 还是 E::f(int)? f('a'); // 正确:唯一的 f(char) 是 D::f(char) }
注释
在任何命名空间作用域中使用指令
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; // 标准 vector vec::vector<int> v2; // 用户自定义 vector // v1 = v2; // 错误:v1 和 v2 是不同类型的对象 { using namespace std; vector<int> v3; // 等同于 std::vector v1 = v3; // 正确 } { using vec::vector; vector<int> v4; // 等同于 vec::vector v2 = v4; // 正确 } }
缺陷报告
以下行为变更缺陷报告被追溯应用于先前发布的C++标准。
| 缺陷报告 | 适用版本 | 发布时行为 | 正确行为 |
|---|---|---|---|
| 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 的解决方案未应用于类和枚举声明 | 已应用 |
参见
| namespace alias | 为现有命名空间创建别名 |