对程序进行编译时,对于函数调用,编译器只要求函数的原型在调用点是可见的,至于函数的定义是否存在不做检查(在对程序进行链接时才检查函数的定义)。类似的,对于对象声明,编译器只要求所属的类定义在声明点是可见的,至于各成员函数的定义是否存在则不进行检查。因此,为了提高程序的可读性和可维护性,我们通常将函数原型和类定义放在头文件(.h文件)中,而函数定义(包括类成员函数的定义)则放在源文件(.cpp文件,又称实现文件)中。
但是,模版编译则有所不同。从本质上说,模版并不是代码,而是指导编译器生成代码的指令,模版实例才是真正的程序代码。编译器看到模版定义的时候,不会立刻产生代码,只有在看到模版的使用(如调用函数模版、使用类模版定义对象或通过对象调用类模板的成员函数)时,才会进行实例化,使用特定的模版实例代码。而为了成功地进行实例化,编译器必须能够使用相应的函数定义,因此,模版编译要求模版的定义和实现采用特别的文件组织方式。
C++语言中定义了两种编译模式:包含编译模式(inclusion compilation model)和 分离编译模式(separate compilation model)。
——摘自《C++语言程序设计》(蒋爱军、刘红梅、王勇、梁小萍编著)P349 13.3.3 模版编译与类模板的实现
一般情况下,模板定义是要全部写在头文件中的。因为编译器在实例化模板的时候,需要见到完整的定义。不过模板也可以用前置声明+显示实例化的方式。只是用法更怪一些,而且用途受限制(没有显示实例化过的类型还是不能用),这个《C++ Template》一书上有讲到。
分离编译模式实现起来比较困难,因此,所有的C++编译器都支持包含编译模式,而只有某些C++编译器支持分离编模式。程序员在编译使用自定义的模版的程序时,需要查阅编译器的用户指南,以确定自己所用的编译器支持那种编译模式。
参考 http://www.cnblogs.com/BensonLaur/p/4322203.html
模板类进行分离式编译:
//test.h
#ifndef _TEST_H_
#define _TEST_H_
template <class T> // 模板参数是 一个 类型
class Test_swap
{
public:
Test_swap(const T & b);
T a;
};
#endif
//test.cpp
#include "test.h"
template <class T>
Test_swap<T>::Test_swap(const T & b)
{
a = b;
}
//必须显式地对其进行特例化,如果删除,因为main中有用到,导致编译不过,报错:undefined reference to Test_swap<unsigned int >…
template class Test_swap<unsigned int >; //对模板参数unsigned int进行特例化
template class Test_swap<double >; // 对double进行特例化
//main.cpp
#include <iostream>
#include "test.h"
using namespace std;
int main(int argc, char** argv)
{
Test_swap<unsigned int > m_a (10); //特例化过的才能使用,如果模板类型是double,则必须在cpp中对double 进行特例化
cout<<" test:"<<m_a.a <<endl;
return 0;
}
模板类的声明和构造函数实现在同一个文件中时,则不需要进行特例化。编译器会自动进行类型匹配。
//test.h
#ifndef _TEST_H_
#define _TEST_H_
template <class T>
class Test_swap
{
public:
Test_swap(const T & b)
{a = b; }
T a;
};
#endif
//main.cpp
#include <iostream>
#include "test.h"
using namespace std;
int main(int argc, char** argv)
{
Test_swap<float > m_a (10。234); //编译器自动进行类型匹配
cout<<" test:"<<m_a.a <<endl;
return 0;
}