命名空间
命名空间提供在大项目中避免名称冲突的方法。
声明于命名空间块内的符号被放入该具名作用域,它避免这些符号被误认为其他作用域中的同名符号。
允许拥有相同名称的多个命名空间块。这些块中的所有声明声明于该具名作用域。
目录 |
[编辑] 语法
namespace ns_name { declarations }
|
(1) | ||||||||
inline namespace ns_name { declarations }
|
(2) | (C++11 起) | |||||||
namespace { declarations }
|
(3) | ||||||||
ns_name:: name
|
(4) | ||||||||
using namespace ns_name;
|
(5) | ||||||||
using ns_name:: name;
|
(6) | ||||||||
namespace name = qualified-namespace ;
|
(7) | ||||||||
namespace ns_name:: name
|
(8) | (C++17 起) | |||||||
[编辑] 解释
[编辑] 命名空间
inline (可选) namespace attr(可选) identifier { namespace-body }
|
|||||||||
inline
|
- | 若存在,则令此命名空间为内联命名空间(见后述)。若原初命名空间定义不使用 inline ,则不能出现在扩展命名空间定义上
|
attr(C++17) | - | 任何数量属性的可选序列 |
identifier | - | 要么是先前未使用的标识符,该情况下这是原初命名空间定义,要么是命名空间名,该情况下这是扩展命名空间定义,要么是 :: 所分隔的外围命名空间指定符,以 identifier 结束,该情况下这是嵌套命名空间定义 (C++17 起) |
namespace-body | - | 任何种类(包含类与函数定义还有嵌套命名空间)声明的可能为空的序列 |
命名空间定义只允许在命名空间作用域,包括全局作用域。
为重打开既存的命名空间(正式而言,为作为扩展命名空间定义),用于命名空间定义的 identifier 查找必须解析为命名空间名(非命名空间别名),该名称声明为外围命名空间作用域,或外围命名空间中内联命名空间的成员。
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 的成员 f 的 V 外定义 // f 的外围命名空间仍是全局命名空间、 Q 与 Q::V { extern void h(); // 这声明 ::Q::V::h } void V::C::m() // V::C::m 的命名空间(及类)外定义 // 外围命名空间是全局命名空间、 Q 与 Q::V { } }
命名空间外定义和再声明仅在声明点后,仅在命名空间作用域,且仅在包围原命名空间的命名空间(包含全局命名空间)允许,而且它们必须使用限定 id 语法 (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 的浮莲子 }
内联命名空间内联命名空间是在其原初命名空间定义使用可选的关键词 多数情形中(列于下方),内联命名空间中的成员被当做如同它们是外围命名空间的成员。此属性是传递性的:若命名空间 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 attr(可选) { namespace-body }
|
|||||||||
attr(C++17) | - | 任何数量属性的可选序列 |
此定义被当做拥有独有名称的命名空间定义,与当前作用域中指名此无名命名空间的 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 (可选) nested-name-specifier unqualified-id ;
|
(C++17 前) | ||||||||
using declarator-list ;
|
(C++17 起) | ||||||||
nested-name-specifier | - | 名称与作用域解析运算符 :: 的序列,以作用域解析运算符结束。单个 :: 代表全局命名空间。
|
unqualified-id | - | id 表达式 |
typename | - | using 声明从基类引入成员类型到类模板时,关键词 typename 可在必须时用于解析依赖名
|
declarator-list | - | 一或多个拥有 typename (可选) nested-name-specifier unqualified-id 形式的声明器的逗号分隔列表。最末声明器可以是省略号,尽管该形式只在导出类定义有意义
|
using 声明能用于引入命名空间成员到其他命名空间和块作用域,或引入基类成员到导出类定义。
拥有多于一个 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 声明不能指名模板 id 、命名空间或有作用域枚举项。 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 } 更泛而言,出现于任何命名空间并用无限定标识符引入名称的声明,始终引入成员到它所在的命名空间,而不到任何外层命名空间。例外是定义于内联命名空间的显式实例化和显式特化:因为它们不引入新名称,它们可以使用外围命名空间中的无限定 id 。 |
(C++14 起) |
[编辑] using 指令
using 指令是拥有下列语法的块声明:
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-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 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; }
[编辑] 缺陷报告
下列更改行为的缺陷报告追溯地应用于以前出版的 C++ 标准。
DR | 应用于 | 出版时的行为 | 正确行为 |
---|---|---|---|
CWG 1838 | C++14 | 外层命名空间的非限定定义,曾能定义声明但不定义于另一命名空间的实体,并用 using 拉入 | 非限定定义始终指涉其命名空间 |
[编辑] 参阅
命名空间别名 | 为既存命名空间创建一个别名 |