命名空间
命名空间提供了一种在大项目中避免名字冲突的方法。
在命名空间块内声明的符号被放入一个具名的作用域中,避免这些符号被误认为其他作用域中的同名符号。
多个命名空间块的名字可以相同。这些块中的所有声明在该具名作用域声明。
语法
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 是 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 {} }
命名空间外的定义和再声明只能出现在
- 声明点后,
- 命名空间作用域中,且
- 作用域的命名空间(包括全局命名空间)包围了原命名空间。
它们也应使用限定标识语法。 | (C++14 起) |
namespace Q { namespace V // V 的原初命名空间定义 { void f(); // Q::V::f 的声明 } void V::f() {} // OK 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() {} // OK:全局命名空间包围 Q
在非局部类 X 中由友元声明所引入的名字会成为 X 的最内层外围命名空间的成员,但它们对于通常的名字查找(无限定或有限定)不可见,除非在命名空间作用域中类定义前后提供与之匹配的声明。这种名字可以通过实参依赖查找找到,其中同时考虑命名空间和类。
在确定名字是否与先前声明的名字冲突时,这种友元声明只考虑最内层的外围命名空间。
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); // 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,且它又继而包含内联命名空间 O,那么 O 的成员能按如同它们是 M 或 N 的成员一样使用。
// C++14 中,std::literals 和它的成员命名空间是内联的 { using namespace std::string_literals; // 令来自 std::literals::string_literals // 的 operator""s 可见 auto str = "abc"s; } { using namespace std::literals; // 令 std::literals::string_literals::operator""s 与 // std::literals::chrono_literals::operator""s 均可见 auto str = "abc"s; auto min = 60s; } { using std::operator""s; // 令 std::literals::string_literals::operator""s 与 // std::literals::chrono_literals::operator""s 均可见 auto str = "abc"s; auto min = 60s; } 注意:上述关于特化的规则允许建立库版本:库模板的不同实现可以在不同的内联命名空间定义,同时仍然允许用户通过主模板的显式特化来扩充父命名空间。 |
(C++11 起) |
无名命名空间
无名命名空间定义是具有下列形式的命名空间定义。
inline (可选) namespace 属性(可选) { 命名空间体 }
|
|||||||||
inline
|
- | (C++11 起) 存在时,此命名空间是内联命名空间 |
属性 | - | (C++17 起) 任意数量的属性的序列 |
此定义被当做一个拥有独有名字的命名空间定义,与当前作用域中指名此无名命名空间的一条 using 指令 (注:隐式添加的 using 指令使有限定查找和无限定查找可以发现该命名空间,但实参依赖查找不在此列)。
namespace { int i; // 定义 ::(独有)::i } void f() { i++; // 自增 ::(独有)::i } namespace A { namespace { int i; // A::(独有)::i int j; // A::(独有)::j } void g() { i++; } // A::(独有)::i++ } using namespace A; // 从 A 引入所有名称到全局命名空间 void h() { i++; // 错误:::(独有)::i 与 ::A::(独有)::i 均在作用域中 A::i++; // OK:自增 A::(独有)::i j++; // OK:自增 A::(独有)::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; // 全局 f 现在作为 ::X::f 可见 using A::g; // A::g 现在作为 ::X::g 可见 using A::g, A::g; //(C++17)OK:命名空间作用域允许双重声明 } 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 声明不能指名模板标识或命名空间,或有作用域的枚举项 (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:函数名 g 隐藏类 g } void func() { int i; using B::i; // 错误:i 声明了两次 void f(char); using B::f; // OK: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; // OK :隐藏 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 指令是拥有下列语法的块声明:
属性(可选) using namespace 嵌套名说明符(可选) 命名空间名 ;
|
(1) | ||||||||
属性 | - | (C++11 起) 应用到此 using 指令的任意数量的属性 |
嵌套名说明符 | - | 名字与作用域解析运算符 :: 的序列,以作用域解析运算符结束。单个 :: 代表全局命名空间。查找此序列中的名字时,查找只考虑命名空间声明
|
命名空间名 | - | 命名空间名。查找此名时,查找只考虑命名空间声明 |
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; // 将所有来自 A 的名称注入全局命名空间 int j; int k; int a = i; // i 是 B::i,因为 B::i 隐藏 A::i } using namespace D; // 注入来自 D 的名称到 C // 注入来自 A 的名称到全局命名空间 int k = 89; // 声明与用 using 引入者等同的名称 OK int l = k; // 歧义:C::k 还是 D::k int m = i; // OK:B::i 隐藏 A::i int n = j; // OK:D::j 隐藏 B::j } }
在使用 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; // OK:声明时与 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++; // OK D::d1++; // OK d2++; // OK,d2 是 D::d2 e++; // OK:因为传递性 using,所以 e 是 E::e f(1); // 错误:歧义:D::f(int) 还是 E::f(int)? f('a'); // OK:f(char) 只能是 D::f(char) }
注解
在任何命名空间作用域中的 using 指令 using namespace std;将命名空间 std
中的所有名字都引入到全局命名空间中(因为全局命名空间是同时包含 std
和任何用户声明命名空间的最近命名空间),这可能导致不合预期的名字冲突。通常认为,在头文件的文件作用域中采用它或其他的 using 指令是不良的实践(SF.7:不要在头文件的全局作用域中写 using namespace)。
示例
此例展示如何用命名空间创建已在 std
命名空间命名的类。
#include <vector> namespace vec { template<typename T> class vector { // ... }; } 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; // OK } { using vec::vector; vector<int> v4; // 同 vec::vector v2 = v4; // OK } return 0; }
缺陷报告
下列更改行为的缺陷报告追溯地应用于以前出版的 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 的解决方案没有应用到类和枚举的声明 | 已应用 |
参阅
命名空间别名 | 为既存命名空间创建一个别名 |