原文:https://blog.csdn.net/pi9nc/article/details/11267031
利用匿名namespace解决C++中重复定义的问题
今天写代码的时候又碰到了C++中多编译单元导致重复定义(multi definition)的链接问题。其实这个问题以前也碰到过几次,急着编译出代码也没有去深究背后的一些知识。今天系统的看了一些资料,算是把这个问题彻底搞清楚了。这里做下简单总结吧:
- C++中有由于模版分离编译等问题,导致常常需要在头文件加入变量定义或者函数定义的代码,从而在链接多编译单元时导致multi-definition重复定义的问题。
- 传统C语言中的static关键在C++中对于类的成员有其他的语义,导致其功能的局限性。
- 在C++中建议使用匿名namespace类实现将一个函数或者变量的定义局限在一个编译单元内,避免multi-definition 的问题。
在C++中的,由于引入了面向对象的概念,导致了有时候在头文件中不得不加入函数实现或者变量定义的代码。比如在大部分编译器上不支持模版分离编译,导致很多模板类的实现只有放在头文件中,像boost等库都大量采用了hpp这种格式实现完全头文件化的库。
但是这种方法经常会导致一个问题就是multi-definition重复定义,在较大型的工程中往往会采用多编译单元的形式生成多个.o文件,然后再用ld链接生成可执行文件。如果多个.o中都include了同一个hpp文件,而该hpp文件又包含了全局变量,类的静态成员等一些变量的定义,那么就会导致gcc的multi-definition报错。在传统的C程序中可以通过static申明一个变量或者函数不生成全局符号来解决这个问题,但是C++中static关键字对于类的成员有了其他语义。因此C++中建议使用匿名namespace来替代static避免multi-definition的问题。看如下一个例子:
----------- test.hpp----------
#include <string>
class
A
{
public:
static
std::string y;
};
std::string A::y = std::string();
----------- test_comm.cpp----------
#include "test.hpp"
void
func() { }
----------- test_main.cpp----------
#include "test.hpp"
void
func();
int
main(int
argc, char
*argv)
{
func();
}
我们在g++上编译就会出现如下错误:
leoxiang@SEC38_64_sles10:~$ g++ -c test_comm.cpp
leoxiang@SEC38_64_sles10:~$ g++ -c test_main.cpp
leoxiang@SEC38_64_sles10:~$ g++ -o test
test_comm.o test_main.o
test_main.o:(.bss+0x0): multiple definition of `A::y'
test_comm.o:(.bss+0x0): first defined here
collect2: ld returned 1 exit
status
下面通过匿名namespace解决这个问题,只需要把test.hpp的实现用匿名namespace包围即可避免重复定义的问题:
----------- test.hpp----------
#include <string>
namespace
{
class
A
{
public:
static
std::string y;
};
std::string A::y = std::string();
}
实际上匿名namespace的作用是把其中的变量都放在了一个随机名字空间中,并且保证改名字空间在多个编译单元中是唯一的。因为匿名namespace中声明或定义的变量函数是全局可见的,所以并不会对自己所在文件的编译造成影响,这就是实现了之前C语言中static关键字的作用,并且具有更好的实用性。
但是匿名namespace也不是完美的,下面这篇文章《C++ 工程实践(1):慎用匿名 namespace》中介绍了使用匿名namespace会导致的两个问题:
- 其中的函数难以设断点,如果你像我一样使用的是 gdb 这样的文本模式 debugger。
- 使用某些版本的 g++ 时,同一个文件每次编译出来的二进制文件会变化,这让某些 build tool 失灵。
- 同时在头文件中使用匿名空间也会对库使用这造成一些陷阱,类似与在C中在头文件中使用静态变量。
即使如此,在Google C++编程规范2.1节中也鼓励使用namespace代替C语言中的static关键字。因此个人觉得一些特殊情况下如果必须在头文件中定义类静态变量或者函数,可以考虑将整个文件代码用匿名namespace包裹,可以较好解决重复定义的问题。
网友评论