只有把模板定义放到每一个引用它的地方,这样才可能完成展开——从这个意义上说,所谓模板定义其实应该算是模板声明,真正的定义是在你确认了模板类型参数之后(由编译器自动生成代码)。
C++中每一个对象所占用的空间大小,是在编译的时候就确定的,在模板类没有真正的被使用之前,编译器是无法知道,模板类中使用模板类型的对象的所占用的空间的大小的。只有模板被真正使用的时候,编译器才知道,模板套用的是什么类型,应该分配多少空间。这也就是模板类为什么只是称之为模板,而不是泛型的缘故。
既然是在编译的时候,根据套用的不同类型进行编译,那么,套用不同类型的模板类实际上就是两个不同的类型,也就是说,stack<int>和stack<char>是两个不同的数据类型,他们共同的成员函数也不是同一个函数,只不过具有相似的功能罢了。如上图所示,很简短的六行代码,用的是STL里面的stack,stack<int>和stack<char>的默认构造函数和push函数的入口地址是不一样的,而不同的stack<int>对象相同的函数入口地址是一样的,这个也反映了模板类在套用不同类型以后,会被编译出不同代码的现象。
所以模板类的实现,脱离具体的使用,是无法单独的编译的;把声明和实现分开的做法也是不可取的,必须把实现全部写在头文件里面。为了清晰,实现可以不写在class后面的花括号里面,可以写在class的外面。
关于c++中类的模板,惯常的用法都是把实现和定义放在头文件里了事。这样的问题是,加大了头文件的长度,减低了模板代码的可读性,还有就是破坏了面向对象的隐藏实现的规则。下面就是这种问题的两个解决方案的总结(取自codeprojecthttp://www.codeproject.com/cpp/templatesourceorg.asp):
假设模板代码如下:
// 模板定义 a.h
#include <stdio.h>
template <class tType>
class TTEST
...{
} ;
// 模板实现 a.cpp
#include "a.h"
template<class tType>
void TTEST<tType>::output()
...{
}
// 调用模块 test.cpp
#include "a.cpp"
int main()
...{
}
注意调用模块test.cpp中,不要包含头文件a.h直接包含a.cpp,这样就能够编译成功。这是由于编译器要建立TTEST<int>这个类型需要模板实现,但从头文件里没找到,所以它会认为实现在其它编译单元中,但它不会报错,而把找实现部分的工作留给了linker。linker同样也是找不到实现,因此就不能生成TTEST<int>。上述第一种解决方法直接包含了实现文件,让compiler直接找到定义和实现,这样的效果跟把定义和实现都放在a.h里是一样的。
第二中方法,类似第一种,只是test.cpp只包含a.h,然后新增一个文件impl.cpp内容如下:
// impl.cpp
#include "a.cpp"
template class TTEST<int> ;
如此一来,也能达到定义与实现分离的目的。只是也要包含a.cpp,另外随着模板实例化不同的类型,impl,cpp中的类型也要跟着改变,有点笨拙的方法。
以上代码在gcc 3.4.4中编译通过。