匿名命名空间

在 C/C++ 的源文件中,为了对外隔离符号,即外界无法通过 extern 声明捕获到这个符号,我们一般会采用 static 声明的方式来处理这个符号。比如

// 采用static声明对外隐藏符号
static void internal_func();

static float internal_value;

这样, internal_funcinternal_value 这两个符号对外就是不可见的了。一般情况下,隐藏符号只通过 static 声明就可以了,但 static 声明也有如下缺点:

1、static 无法修饰类型,比如这样的类型声明就是错误的。

// 注意:这段代码是非法的
static class Widget {
public:
    ...
};

因此,如果想要隐藏 Widget 这个类,就不能用 static 声明的方式。

2、static 这个关键词用处过多,在不同的地方修饰的变量,其含义完全不同,容易造成混淆。比如下面定义的三个 static 变量,其含义就完全不同

class Widget
{
private:
    static float count;
};

static const float PI = 3.141592654;

void test()
{
    static int i = 0;
    ...
}

3、由于 static 声明会使得符号没有外部链接属性,因此某些模板参数将不能使用这个符号,比如

template<typename T, const int& N>
class Widget
{
public:
    ...
private:
    T data[N];
    ...
};

static const int kMaxSize = 30;

Widget<int, kMaxSize> w;   // 报错,因为kMaxSize并不具备外部链接属性

为了克服上述三个缺点,就需要引入另一种隐藏符号的方式——匿名命名空间。我们先来看这样一段声明

// 采用匿名命名空间对外隐藏符号
namespace {

void internal_func();

float internal_value;

}

在这段声明中,注意我们并没有指定 namespace 是什么。这种没有为 namespace 指定名字的命名空间就是所谓的“匿名命名空间”。

实际上,编译器会自动为每一个匿名命名空间生成一个唯一的命名空间名字,只不过由于我们并不知道这个名字是什么,从而使得外部无法跟踪到具体命名空间下的符号,这样符号才会看起来像是被隐藏了。可见,匿名命名空间下的符号实际上是具有外部链接属性的,本质上并非一种真正的对外隐藏符号的方式。正是因为有这样的特性,上述 static 的第三点缺点也就不存在了。而如果把这个匿名命名空间看成是一个普通的 namespace 声明,就会发现 static 的第一和第二缺点也不存在了。

当然,我们也不要过于依赖匿名命名空间,匿名命名空间在 gdb 断点的时候也会带来很多麻烦,比如断点不可用、断点错误等。因此,还是需要根据不同的场合选用合适的方式来隐藏符号。

留下评论