Jason Pan

C++模板 定义和实例化分别该放在哪里?

潘忠显 / 2020-11-06


关于C++的模板,首先要理解以下三点:

  1. 模板不是函数,也不是类,而是供编译器生成一族函数或者类的"pattern"
  2. 如果要生成一个对应类型的函数或者类,可以根据模板+指定类型来生成
  3. 由于"分离编译模式",cpp里边的模板,如果在自己的文件里边没有被实例化,在别的地方是看不到具体的实现细节的

关于模板的代码,通常会有四部分:声明、定义、显式实例化、被调用位置

如果要外部使用,声明一定是在头文件中,比如foo放在头文件"lib.h"中:

// lib.h
template<typename T>
T foo(T t);

被调用位置在除了在自己所在的cpp之外,大部分在外部cpp文件中调用,比如在"main.cc"中调用typenameint的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