C++匿名命名空间的用法(附带实例)
程序越大,当程序链接到多个编译单元时,发生命名冲突的可能性也就越大。在源文件某编译单元中声明的函数或变量可能会与在另一个编译单元中声明的类似函数或变量发生冲突。
这是因为所有没有被声明为静态的符号都有外部链接,而且它们的名称在整个程序中必须是唯一的。针对此问题:
下面的例子显示在两个不同的编译单元中有两个名为 print() 的函数,它们都定义在匿名命名空间中:
在 C 语言中,解决这个问题的方法是,将函数或变量声明为静态的,并将其链接从外部更改为内部。在这种情况下,它的名称不再被导出到编译单元之外,避免了链接问题。
在 C++ 中,一种合适的解决方法是使用匿名命名空间。当像前面那样定义一个命名空间时,编译器会把它转换成如下形式:
其次,使用 using 关键字在当前命名空间引入 unique_name 命名空间中的所有内容。最后,名称由编译器生成的命名空间的定义与原始源代码中的一样(当时命名空间是匿名的)。
通过在匿名命名空间中定义编译单元局部 print() 函数,它们仅具有本地可见性,但它们的外部链接不再产生链接错误,因为它们现在具有外部唯一名称。
匿名命名空间还可用于更复杂的情形,比如模板。
一方面,在 C++11 之前,模板非类型实参不能是具有内部链接的名称,因此不可能使用静态变量。另外,匿名命名空间中的符号具有外部链接,可以用作模板实参。虽然 C++11 中取消了模板非类型实参的链接限制,但它仍然存在于最新版本的 VC++ 编译器中。该问题如下所示:
这是因为所有没有被声明为静态的符号都有外部链接,而且它们的名称在整个程序中必须是唯一的。针对此问题:
- 典型的 C 解决方案是将这些符号声明为静态的,将它们的链接从外部更改为内部,从而使它们成为编译单元的本地符号;
- 另一种方法是在名称前面加上它们所属的模块或库的名称。
C++ 匿名命名空间的使用方式
当需要将全局符号声明为静态的以避免链接问题时,应该首选匿名命名空间:- 在源文件中声明一个匿名命名空间;
- 将全局函数或变量的定义放在匿名命名空间中,但不要将它们设为 static。
下面的例子显示在两个不同的编译单元中有两个名为 print() 的函数,它们都定义在匿名命名空间中:
// file1.cpp namespace { void print(std::string message) { std::cout << "[file1] " << message << '\n'; } void file1_run() { print("run"); } } // file2.cpp namespace { void print(std::string message) { std::cout << "[file2] " << message << '\n'; } void file2_run() { print("run"); } }
C++ 匿名命名空间的工作原理
当函数在编译单元中被声明时,它具有外部链接属性。这意味着来自两个不同编译单元的两个具有相同名称的函数将产生链接错误,因为不可能有两个具有相同名称的符号。在 C 语言中,解决这个问题的方法是,将函数或变量声明为静态的,并将其链接从外部更改为内部。在这种情况下,它的名称不再被导出到编译单元之外,避免了链接问题。
在 C++ 中,一种合适的解决方法是使用匿名命名空间。当像前面那样定义一个命名空间时,编译器会把它转换成如下形式:
// file1.cpp namespace __unique_name_ { using namespace __unique_name_; namespace __unique_name_ { void print(std::string message) { std::cout << "[file1] " << message << '\n'; } void file1_run() { print("run"); } } }首先,它声明了一个具有唯一名称的命名空间(名称是什么以及如何生成该名称是编译器实现的细节,我们无须关注),此时命名空间还是空的,这一行代码的目的是构建命名空间。
其次,使用 using 关键字在当前命名空间引入 unique_name 命名空间中的所有内容。最后,名称由编译器生成的命名空间的定义与原始源代码中的一样(当时命名空间是匿名的)。
通过在匿名命名空间中定义编译单元局部 print() 函数,它们仅具有本地可见性,但它们的外部链接不再产生链接错误,因为它们现在具有外部唯一名称。
匿名命名空间还可用于更复杂的情形,比如模板。
一方面,在 C++11 之前,模板非类型实参不能是具有内部链接的名称,因此不可能使用静态变量。另外,匿名命名空间中的符号具有外部链接,可以用作模板实参。虽然 C++11 中取消了模板非类型实参的链接限制,但它仍然存在于最新版本的 VC++ 编译器中。该问题如下所示:
template <int const& Size> class test {}; static int Size1 = 10; namespace { int Size2 = 10; } test<Size1> t1; test<Size2> t2;在此代码片段中,t1 变量的声明导致了一个编译错误,因为非类型实参表达式 Size1 有内部链接。t2 变量的声明是正确的,因为 Size2 有外部链接(注意,用 Clang 和 GCC 编译此代码片段不会产生错误)。