为什么要编写纯头文件代码呢?传统.cpp代码要求使用者要么使用底层运行库兼容的预编译版本,要么重新编译,操作起来非常麻烦,而纯头文件代码则简单得多,直接#include就可以了。
如果你认真研究过C++的模板,你会发现它们的函数体是推荐定义在头文件中的,这样每当用到时便可以实例化它们。链接时则会遇到一个问题,实例化的代码可能在不同.obj/.o文件中产生重复定义,而传统上C++符号是不允许重复定义的,怎么办呢?实际上,编译器会把它们标记为可合并符号,链接时会随机选择一个链接,所以并不会产生重复定义问题。
inline这个关键字也可以产生同样的效果。inline在C++中表示推荐内联,因此应该被定义在头文件中,并且只要不标记为static,它就是extern的,标准规定了它们在所有.obj/.o文件中具有相同的地址,也就是可以产生可合并符号。
在C++17之前全局变量和类成员静态变量(其实是类命名空间内的全局变量)并不能声明为inline。编译器厂商早就想到了这个问题,并提供了不同的编译器扩展。在Windows平台,可以使用__declspec(selectany)声明可合并符号,但是仅适用于全局变量,不适用于全局函数。在Linux平台,可以使用__attribute__((weak))。
另外要注意的是C语言不提供类似的机制,C语言的inline并不会产生可合并符号。
有了这些机制的支持,我们就能在头文件中生成可合并符号,进而编写纯头文件代码了。
#if __cpp_inline_variables >= 201606L
#define INLINEGLOBAL inline // C++17
#elif defined(_MSC_VER)
#define INLINEGLOBAL __declspec(selectany) // Visual C++
#else
#define INLINEGLOBAL __attribute__((weak)) // GCC/Clang
#endif
namespace MyLib {
INLINEGLOBAL int global_num = 1;
inline int func(int a, int b)
{
static int static_num = 0;
return a + b + static_num++;
}
class MyClass {
public:
static int static_num;
MyClass();
};
INLINEGLOBAL int MyClass::static_num = 0;
inline MyClass::MyClass()
{
static_num++;
}
} // namespace MyLib