命名空间

< cpp‎ | language

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

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

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

语法

namespace 命名空间名 { 声明序列 } (1)
inline namespace 命名空间名 { 声明序列 } (2) (C++11 起)
namespace { 声明序列 } (3)
命名空间名::名字 (4)
using namespace 命名空间名; (5)
using 命名空间名::名字; (6)
namespace 名字 = 有限定命名空间 ; (7)
namespace 命名空间名::inline(C++20 起)(可选) 名字 { 声明序列 } (8) (C++17 起)
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 { ... } } }

namespace A::B::inline C { ... } 等价于 namespace A::B { inline namespace C { ... } }inline 可出现于除首个以外的每个命名空间名之前:namespace A::inline B::C {} 等价于 namespace A { inline namespace B { namespace C {} } }

(C++20 起)

解释

命名空间

inline(可选) namespace attr(可选) 标识符 { 命名空间体 }
inline - 若存在,则令此命名空间为内联命名空间(见下文)。若原初命名空间定义不使用 inline,则不能出现在扩展命名空间定义上使用
attr(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 的成员 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 的友元
}

内联命名空间

内联命名空间是在其原初命名空间定义中使用了可选的关键词 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 attr(可选) { 命名空间体 }
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(可选) 嵌套名说明符 无限定标识 ; (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 指令是拥有下列语法的块声明

attr(可选) using namespace 嵌套名说明符(可选) 命名空间名 ; (1)
attr(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: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;
}


缺陷报告

下列更改行为的缺陷报告追溯地应用于以前出版的 C++ 标准。

DR 应用于 出版时的行为 正确行为
CWG 1838 C++14 外层命名空间的非限定定义,曾能定义声明但不定义于另一命名空间的实体,并用 using 拉入 非限定定义始终指涉其命名空间

参阅

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