命名空间
命名空间提供了在大项目中避免名字冲突的一种方法。
声明于命名空间块内的符号被放入一个具名的作用域中,避免这些符号被误认为其他作用域中的同名符号。
允许具有相同名字的多个命名空间块。这些块中的所有声明声明于该具名作用域。
命名空间
命名空间的定义:只允许在命名空间作用域,包括全局作用域中出现
重打开一个已经定义的命名空间(某个外围命名空间或外围命名空间中的内联命名空间的成员),需要使用命名空间名(而非别名)。
命名空间体内声明的所有名字,都成为该命名空间的成员,无论此命名空间定义是原初命名空间定义(引入 标识符 者),还是扩展命名空间定义(“重打开”已定义命名空间者)
声明于命名空间体内的命名空间成员,可以在其外部用显式限定进行定义或再声明:
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 的成员 f 的 V 外定义
// 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 的最内层外围命名空间的成员,但它们对于通常的名字查找(无限定或有限定)不可见,除非在命名空间作用域提供与之匹配的声明,不论在类之前还是之后均可。这种名字可通过 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); // A::X::f 通过 ADL 找到
}
void f(X) {} // A::f 的定义
void h(int) {} // A::h 的定义
// A::f 、 A::g 与 A::h 现在在命名空间作用域可见
// 而且它们亦是 A::X 与 A::X::Y 的友元
}
具名命名空间
namespace
命名空间名 { 声明序列 }
内联命名空间(C++11)
inline
namespace
命名空间名 { 声明序列 }
内联命名空间是在其原初命名空间定义中使用了可选的关键词 inline
的命名空间。
许多情况下(列于下方),内联命名空间的成员都被当做如同它们是其外围命名空间的成员一样。这项性质是传递性的:若命名空间 N 包含内联命名空间 M,它又继而包含内联命名空间 O,则 O 的成员能按如同它们是 M 或 N 的成员一样使用。
- 在外围命名空间中,隐含地插入了一条指名内联命名空间的 using 指令(类似于无名命名空间的隐式 using 指令)
- 实参依赖查找中,当将命名空间添加到关联命名空间集合时,亦添加其内联命名空间,而当添加内联命名空间到关联命名空间列表时,亦添加其外围命名空间。
- 内联命名空间的每个成员,都能按照如同它是外围命名空间的成员一样,进行部分特化、显式实例化或显式特化。
- 检验外围命名空间的有限定名字查找,将包含来自其各个内联命名空间的名称,即使同一名称已存在于外围命名空间也是如此。
//C++11起
{ // 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;
}
注意:上述关于特化的规则允许建立库版本:库模板的不同实现可定义于不同内联命名空间,同时仍然允许用户通过主模板的显式特化来扩充父命名空间。
无名命名空间
namespace
{ 声明序列 }
此定义被当做一个拥有独有名字的命名空间定义 + 当前作用域中指名此无名命名空间的一条 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 起:无名命名空间以及所有直接或间接在无名命名空间内声明的命名空间,都具有内部连接,这表示声明于无名命名空间内的任何名字都具有内部连接。
嵌套命名空间(C++17)
namespace
命名空间名 ::inline
(C++20 起)(可选) 名字 { 声明序列 }
inline 可出现于除首个以外的每个命名空间名之前
嵌套命名空间定义:
namespace A::B::C { ... }
namespace A { namespace B { namespace C { ... } } }
namespace A::B::inline C { ... }
namespace A::B { inline namespace C { ... } }
namespace A::inline B::C {}
namespace A { inline namespace B { namespace C {} } }
using
using 指令:从 using 指令之后到指令出现的作用域结尾为止,以对任何名字的无限定名字查找的视点来说,来自 命名空间名 的任何名字均可见,如同它声明于同时含有该 using 指令和 命名空间名 两者的最接近外围命名空间作用域一样。
using 声明:令来自命名空间 命名空间名 的符号 名字 对无限定名字查找可见,如同将它声明于包含该 using 声明的相同的类作用域、块作用域或命名空间之中一样。
using 声明
引入定义于别处的名称到 using 声明所在的声明性区域。
using
typename
(可选) 嵌套名说明符 无限定标识;
(C++17 前)using
声明符列表;
(C++17 起)
using 声明可用于
- 将命名空间的成员引入到其他命名空间和块作用域中
- 将基类的成员引入到派生类定义中
- 将枚举项引入命名空间、块或类作用域中 (C++20 起)。
(C++17 起)拥有多于一个 using 声明符的 using 声明,等价于对应的单个 using 声明符的 using 声明的序列。
对于在派生类定义中的用法,见 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) 冲突
}
//C++14起
若某个实体被声明,但未在某内层命名空间中定义,然后在外层命名空间中通过 using 声明予以声明,然后在外层命名空间中再出现拥有相同非限定名的定义,则该定义是外层命名空间的成员,且与 using 声明冲突:
namespace X { namespace M { void g(); // 声明,但不定义 X::M::g() } using M::g; void g(); // 错误:试图声明与 X::M::g() 冲突的 X::g }
更一般地,出现于任何命名空间作用域中并用无限定标识符引入名字的声明,始终向它所在的命名空间中引入一个成员,而并非向任何其他命名空间引入。例外情况是对定义于内联命名空间的主模板进行的显式实例化和显式特化:因为它们不引入新名字,它们在外围命名空间中可以使用无限定标识。
using 指令
attr(C++11可选) using
namespace
嵌套名说明符(可选) 命名空间名 ;
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:e 是 E::e,因为传递性 using
f(1); // 错误:歧义:D::f(int) 或 E::f(int)?
f('a'); // OK:仅有的 f(char) 是 D::f(char)
}
注解
在任何命名空间作用域中的 using 指令 using namespace std;
,将命名空间 std
中的所有名字都引入到全局命名空间中(因为全局命名空间是同时包含 std
和任何用户声明命名空间的最近命名空间),这可能导致不合预期的名字冲突。通常认为,在头文件的文件作用域中采用它或其他的 using 指令是不良的实践。
示例
此例展示如何用命名空间创建已命名于 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; // OK
}
{
using vec::vector;
vector<int> v4; // 同 vec::vector
v2 = v4; // OK
}
return 0;
}
参考
https://zh.cppreference.com/w/cpp/language/namespace