命名空间

来自cppreference.com
< cpp‎ | language

命名空间提供了一种在大项目中避免名字冲突的方法。

在命名空间块内声明的符号被放入一个具名的作用域中,避免这些符号被误认为其他作用域中的同名符号。

多个命名空间块的名字可以相同。这些块中的所有声明在该具名作用域声明。

语法

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 起)
1) 命名空间 命名空间名具名命名空间定义
2) 命名空间 命名空间名内联命名空间定义命名空间名 内的声明将在其外围命名空间可见。
3) 无名命名空间定义。其成员具有从声明点到翻译单元结尾的潜在作用域,并具有内部连接
4) 命名空间名(还有类名)可以出现在作用域解析运算符的左侧,作为有限定的名字查找的一部分。
5) using 指令:从 using 指令之后到指令出现的作用域结尾为止,以对任何名字的无限定名字查找的视点来说,来自 命名空间名 的任何名字均可见,如同它在同时含有该 using 指令和 命名空间名 两者的最接近外围命名空间作用域声明一样。
6) using 声明:令来自命名空间 命名空间名 的符号 成员名无限定名字查找可见,如同将它在包含该 using 声明的相同的类作用域、块作用域或命名空间之中声明一样。
7) 命名空间别名定义(namespace-alias-definition):令 名字 成为另一命名空间的同义词:见命名空间别名
8) 嵌套命名空间定义:namespace A::B::C { ... } 等价于 namespace A { namespace B { namespace C { ... } } }
9) 嵌套内联命名空间定义:namespace A::B::inline C { ... } 等价于 namespace A::B { inline namespace C { ... } }inline 可以在首个以外的每个命名空间名之前出现:namespace A::inline B::C {} 等价于 namespace A { inline namespace B { namespace C {} } }

解释

命名空间

inline(可选) namespace 属性(可选) 标识符 { 命名空间体 }
inline - (C++11 起) 存在时,此命名空间是内联命名空间(见下文)。如果原初命名空间定义不使用 inline,那么不能出现在扩展命名空间定义
属性 - (C++17 起) 任意数量的属性的可选序列
标识符 - 下列之一:
  • 先前未使用过的标识符,此时这是原初命名空间定义
  • 命名空间名,此时这是扩展命名空间定义
  • :: 分隔的外围命名空间说明符,以 标识符 结束,此时这是嵌套命名空间定义
(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 的友元
}

内联命名空间

内联命名空间是在其原初命名空间定义中使用了可选的关键词 inline 的命名空间。

在许多情况下(列于下方),内联命名空间的成员都被当做如同它们是其外围命名空间的成员一样。这项性质是传递性的:如果命名空间 N 包含内联命名空间 M,且它又继而包含内联命名空间 O,则 O 的成员能按如同它们是 M 或 N 的成员一样使用。

  • 在外围命名空间中,隐含地插入了一条指名内联命名空间的 using 指令(类似于无名命名空间的隐式 using 指令)
  • 实参依赖查找中,当一个命名空间被添加到关联命名空间集合时,它的内联命名空间也会一起被添加,且当一个内联命名空间被添加到关联命名空间列表时,它的外围命名空间也会一起被添加。
  • 内联命名空间的每个成员,都能按照如同它是外围命名空间的成员一样,进行部分特化、显式实例化或显式特化。
  • 检验外围命名空间的有限定名字查找将包含来自其各个内联命名空间的名称,即使同一名称已在外围命名空间存在。
{ // 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
}

更一般地,在任何命名空间作用域中出现并用无限定标识符引入名字的一个声明始终向它所在的命名空间中引入一个成员,而并非向任何其他命名空间引入。例外情况是对在内联命名空间定义的主模板进行的显式实例化和显式特化:因为它们不引入新名字,它们在外围命名空间中可以使用无限定标识。

(C++14 起)

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++ 标准。

DR 应用于 出版时的行为 正确行为
CWG 1838 C++14 外层命名空间的非限定定义,曾能定义一个

在另一命名空间声明但不定义的实体,并用 using 拉入

非限定定义始终指涉其命名空间

参阅

命名空间别名 为既存命名空间创建一个别名