C++模板 定义和实例化分别该放在哪里?
潘忠显 / 2020-11-06
关于C++的模板,首先要理解以下三点:
- 模板不是函数,也不是类,而是供编译器生成一族函数或者类的"pattern"
- 如果要生成一个对应类型的函数或者类,可以根据模板+指定类型来生成
- 由于"分离编译模式",cpp里边的模板,如果在自己的文件里边没有被实例化,在别的地方是看不到具体的实现细节的
关于模板的代码,通常会有四部分:声明、定义、显式实例化、被调用位置。
如果要外部使用,声明一定是在头文件中,比如foo放在头文件"lib.h"中:
// lib.h
template<typename T>
T foo(T t);
被调用位置在除了在自己所在的cpp之外,大部分在外部cpp文件中调用,比如在"main.cc"中调用typename
为int
的foo:
#include "lib.h"
int main() { return foo(1); }
如果将定义放在lib.cc中,如果不进行显式实例化,int foo<int>
这个函数是没有定义的。
// lib.cc
template<typename T>
T foo(T t) {return t;}
对main.cc来说,编译时,知道会有int foo<int>(int)
这一函数;但是在链接时,因为没有找到这个函数的定义,会报如下的链接错误。
main.cc:(.text+0xa): undefined reference to `int foo<int>(int)'
collect2: error: ld returned 1 exit status
记住最初的关于模板的三点,我们解决上述问题可以有接下来介绍的两种方法。
显式实例化所有可能用到的函数,链接的时候就能找到符号
// lib.cc
template<typename T>
T foo(T t) {return t;}
template int foo(int);
# gcc -c lib.cc
> nm lib.o
0000000000000000 W _Z3fooIiET_S0_
模板定义放到头文件,使用者均可自己实例化
模板的定义,就是为了能够尽可能的灵活。上边的实现方法,要求模板提供者实例化所有可能的类型,一定程度上违背了这个目的
// lib.h
template<typename T>
T foo(T t) {return t;}
# gcc main.cc
$ nm a.out | grep foo
00000000004004b3 W _Z3fooIiET_S0_
实践
GCC STL 所有模板定义都放到头文件中
STL的例子 <std_vector.h>
.inc
文件
有些模板实现的很长,会在.h
文件中存放声明,但是具体实现会写在.inc
文件中,然后在文件中include这个inc文件。我们自己的老项目,好多都是这么写的 ;-)
// in t.h
#include "t.inc"
实际上,.inc文件,更多的被用在与环境相关的区分头文件内容的场景中。
比如在LLVM项目中,不同平台的文件分开存在两个.inc文件中:
// Include the platform-specific parts of this class.
#ifdef LLVM_ON_UNIX
#include "Unix/Signals.inc"
#endif
#ifdef LLVM_ON_WIN32
#include "Windows/Signals.inc"
#endif
又比如Google protobuf项目中:
#include <google/protobuf/port_def.inc>
而"port_def.inc"中的内容部分如下:
#ifdef _MSC_VER
#define PROTOBUF_LONGLONG(x) x##I64
#define PROTOBUF_ULONGLONG(x) x##UI64
#define PROTOBUF_LL_FORMAT "I64" // As in printf("%I64d", ...)
#else
// By long long, we actually mean int64.
#define PROTOBUF_LONGLONG(x) x##LL
#define PROTOBUF_ULONGLONG(x) x##ULL
// Used to format real long long integers.
#define PROTOBUF_LL_FORMAT \
"ll" // As in "%lld". Note that "q" is poor form also.
#endif