模板的链接
在链接过程中,模板让事情变得更复杂。
首先了解最基本的一个事实:单一定义原则在考察整个程序时,只关心符号的定义数量。对于类、枚举和模板,允许在整个程序的不同翻译单元中多次出现定义。当然,这同样要求每个定义是完全一致的,否则导致未定义行为。
因此直接将模板定义放到头文件里是没有问题的。即便它被不同的翻译单元包含,最终链接的时候也不会给出重复定义的错误。
那么可不可以把模板的定义与声明分开呢?就像这样,最后链接的时候再给出 mylib.cpp
中的模板定义:
// mylib.h
template<typename T>
void f(T t);
// mylib.cpp
template<typename T>
void f(T t) {
// do something...
}
答案是否定的。这种写法会导致链接错误。这是因为模板的实例化机制导致的。为了讨论方便,假设 a.cpp
使用了 f
模板:
这里使用了 f(42);
,于是类型实参推导为 int
,即需要一个 f<int>
的实例化结果。但是,mylib.h
并没有给出 f
的定义,所以需要在链接的时候去找 f<int>
的定义。
然后 mylib.cpp
给出了模板 f
的定义。但是,mylib.cpp
并没有以任何方式使用 f
模板,所以不会进行 f
的任何实例化。最后的结果就是,mylib.cpp
单独编译后不会存在一个叫做 f<int>
的定义。
两者链接的时候,a.o
缺失了 f<int>
定义,但 mylib.o
也没有这个定义。然后,链接失败,给出未定义错误:
ld: a.o:a.cpp:(.text+0x13): undefined reference to `void f<int>(int)'
那有没有什么应急的办法呢?问题出在了 mylib.cpp
不会实例化 f<int>
,那么就需要强行让它实例化这样一个函数出来。我们过去通过使用函数调用表达式的方式来实例化一个模板,称为“隐式实例化”,而下面这种明明白白地告诉编译器要实例化的语法,称为“显式实例化”:
// mylib.cpp
template<typename T>
void f(T t) {
// do something...
}
// 显式实例化 f<int>
template void f<int>(int t);
// (实参列表 <int> 可省略,从形参推导)
这种语法
template 返回值类型 函数模板名<模板实参列表>(函数形参列表); template classstruct 类模板名<模板实参列表>;
称为显式实例化定义:它既不是模板声明也不是函数或类声明,就单纯是一种不引入名字的、迫使编译器生成一份实例化结果的语法。当有了这样一个显式实例化定义后,刚才的代码也就能够链接通过了。
但这种语法用在库里面是不合适的:你不知道用户会用哪些模板实参来实例化。如果用了 main.cpp
内自定义的一个类型,那就一点办法也没有了。所以,设计模板时,总是建议将定义直接放在头文件。
现在来考察成员函数。不管是类模板的成员函数,还是类的成员函数模板,亦或是类模板的成员函数模板,它们都需要实例化。一旦需要实例化,就没法把定义分到单独的翻译单元去。所以,有关模板的成员函数,也请在头文件中写下。至于是类外定义还是类内定义,这是无所谓的,取决于你的编码习惯。
// mylib.h
template<typename T>
struct A {
// 直接类内定义...
void f() {
// do something...
}
void g();
};
// ...或者类外定义,但仍然在头文件中
template<typename T>
void A<T>::g() {
// do something...
}
最后一样是非常容易忽略的:模板类的友元函数。但它实在是太复杂了,以至于我需要新开一节来讲。