比喻为文件系统

C++ 的 namspace 机制可以减少全局重名符号的冲突,方便项目管理。

我个人觉得,namespace 的设计机制和文件系统的索引机制非常类似。

路径检索

众所周知,我们可以使用 :: 配合具体的 namespace 名称来索引某个具体的符号,如下所示:

#include <iostream>
namespace A {
    namespace B {
        int value = 514;
    }
}
 
void pr_val() {
    printf("value: %d\n", A::B::value);
}
 
int main()
{
    pr_val();
    return 0;
}

为了方便描述,我们称 pr_value 中的 A::B::路径 。直观上,我们顺着路径一个个 namspace 找到我们需要的 value 。

如果 pr_value 本身在某个 namsepace 中,那么应该遵循什么规则呢?核心是这样的,以当前 namespace 根据路径开始索引,如果没有索引到,就在上一级 namespace 中根据路径开始索引。如下所示:

#include <iostream>
 
namespace A {
    namespace B1 {
        int value1 = 514;
        int value2 = 515;
    }
 
    namespace B2 {
        namespace B3 {
            int value1 = 516;
            }
 
            void pr_val();
        }
    }
 
void A::B2::pr_val()
{
    // 当前路径为 A::B2 ,根据 B3 可以获得绝对路径 A::B2::B3::value1 
    printf("A::B2::B3::value1: %d\n", B3::value1);
    // 当前路径为 A::B2 ,根据 B1 获得 A::B2::B1 ,然而并不存在目标
    // 退回父 namsepace ,路径为 A ,根据 B1 获得 A::B1 ,找到目标
    printf("A::B1::value2: %d\n", B1::value2);
    // 使用绝对路径
    printf("A::B1::value1: %d\n", ::A::B1::value1);
}
 
 
 
int main()
{
    A::B2::pr_val();
    return 0;
}

需要注意的是,上文说的 “没有找到” 指的是没有找到对应的 namespace 而非 namespace 里具体的符号,到找不到 namespace 的时候会发生“回退”现象,但是如果找到了 namespace 而没有找到其中的符号,则不会发生回退现象,如下所示:

#include <iostream>
 
namespace A {
    namespace B1 {
        int value1 = 514;
        int value2 = 515;
    }
 
    namespace B2 {
        namespace B1 {
            int value1 = 516;
        }
 
        void pr_val();
    }
}
 
void A::B2::pr_val()
{
    // 当前路径为 A::B2 ,根据 B1 可以获得绝对路径 A::B2::B1::value1 
    printf("A::B2::B1::value1: %d\n", B1::value1);
    // ERROR
    printf("A::B1::value2: %d\n", B1::value2);
    // 当前路径为 A::B2 ,根据 B1 获得 A::B2::B1
    // 存在 A::B2::B1 但是不存在 A::B2::B1::value2
    // 此时并不会回退到 A::B1::value2 中
}
 
int main()
{
    A::B2::pr_val();
    return 0;
}

总的来说,这种方式是一种 “相对路径” 的查找法,但是和 Linux 文件系统的路径不同,它不需要显式指定 ../bro 来去索引兄弟文件夹下的内容,而是在子文件夹没有的情况时,自动搜索兄弟文件夹。

其实我们也可以 :: 开头的命令来表示 “绝对路径” ,如上所示。

这种 namespace 的特殊路径检索方法是构成各种 namespace 奇技淫巧的核心。

匿名 Namespace

当定义一个命名空间时,可以忽略这个命名空间的名称:

namespace {
    char c;
    int i;
    double d;
}

编译器在内部会为这个命名空间生成一个唯一的名字,而且还会为这个匿名的命名空间生成一条 using 指令。所以上面的代码在效果上等同于:

namespace __UNIQUE_NAME_ {
    char c;
    int i;
    double d;
}
using namespace __UNIQUE_NAME_;

这些名称具有 internal 链接属性,这和声明为 static 的全局名称的链接属性是相同的,即名称的作用域被限制在当前文件中,无法通过在另外的文件中使用 extern 声明来进行链接。

需要注意的是,这是一个语法的特殊用法,并没法简单用普通的 namespace 机制解释,namespace 本身是具有全局可见性的,而 using 指令只有 internal 属性。

C++ 提倡使用匿名 Namespace 代替原本的 static ,有如下原因:

  • 减轻 static 的多重语义负担
  • 允许嵌套
  • 可以将 class declare 赋予 internal 属性

“快捷方式”

我们在 C++ 中常会见到使用 using 的关键字的情况,如下所示:

#include<iostream>
using namespace std;
 
namespace A {
    namespace B {
        int value = 5;
    }
 
    namespace C {
        int value = 6;
    }
}
 
using A::B::value;
// using A::C::value; ERROR!
 
int main()
{
    cout << value << endl;
}

使用 using 可以避免繁琐的命名空间书写。

但是有些事情一直困惑我,“使用 using 会不会导致符号的重复”,其实是有概率的,但是这种情况并不会发生符号的覆盖,而是会直接报错。所以不用杞人忧天,去担心当符号出现重复时的 UB 。

我们可以将 using 理解为“快捷方式的创建”,可以将复杂的文件路径压缩到当前文件夹。

using namespace 的用法更像是挂载,这个 namespace 下的所有符号都可以使用。这种挂载同样不允许符号的重复。