在 C/C++ 的源文件中,为了对外隔离符号,即外界无法通过 extern
声明捕获到这个符号,我们一般会采用 static
声明的方式来处理这个符号。比如
// 采用static声明对外隐藏符号
static void internal_func();
static float internal_value;
这样, internal_func
和 internal_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 断点的时候也会带来很多麻烦,比如断点不可用、断点错误等。因此,还是需要根据不同的场合选用合适的方式来隐藏符号。