C++ 模板的编译 以及 类模板内部的实例化
在C++中,编译器在看到模板的定义的时候,并不立即产生代码,只有在看到用到模板时,比如调用了模板函数 或者 定义了类模板的
对象的时候,编译器才产生特定类型的代码。
一般而言,在调用函数的时候,只需要知道函数的声明即可;
在定义类的对象时,只需要知道类的定义,不需要成员函数的定义。
但是,这对于模板编译是不奏效的,模板要进行实例化,则必须能够访问定义模板的源代码,当调用函数模板以及类模板的成员函数
的时候,需要知道函数的定义。
标准C++对于模板的编译提供了两种策略:
相同之处:“将类定义以及函数声明放在头文件中,而函数定义以及成员函数的定义放在源文件中”。
不同之处:编译器怎样使用来自源文件的定义。
包含编译模型:
编译器必须看到用到的所有的模板的定义。
一般可以在声明类模板以及函数模板的头文件中,添加include语句,指示该定义可用。即:
#include “File.cc” // File.cc是包含了相关函数实现的源文件
优点:保证了源文件与定义文件的分离。
缺点:在任何使用到该模板的源文件中,编译时,编译器都会为其实例化一份,也就意味着同一个函数模板可能有多份实例化,在链接的时候,编译器会选择一份实例化,扔掉其他的。
显然,这整个过程是很费时的。
分别编译模型:
编译器会跟踪相关模板的定义,因此我们必须事先告诉编译器,哪些模板是需要记住的。 使用 export关键字可以做到这一点。
每个模板只能使用export关键字一次!!
因此: 1 在函数模板的定义是指明该函数是可导出的; 2 在类的实现文件中使用export关键字,(如果在类定义的文件中使用,那么该头文件只能被某个源文件包含一次!!)。
即: export template<typename T> compare (const T &, const T &) { …. } // 在函数的实现体指明export
export template<class T> class myClass; // 在类的实现文件中使用export
注: 虽然有点多余,但是还是说一下,类的定义文件以及实现文件,定义文件是指定义类的成员变量(即属性)的文件。实现文件是实现类的接口的文件。
值得注意的是:导出类模板的成员自动为导出的。
可以指定类模板的个别成员是可导出的,那么那些不可导出的成员必须遵循 包含编译模型,即定义必须放在定义类模板的文件中。
编译模板本身是很复杂的工作,但是这对用户而言是透明的,全部交给了编译器来承担,但是对于模板用户来说,仍然有一些难点。
在模板中,包含两种名字:依赖于模板形参的名字,以及不依赖于模板形参的名字。
对于模板的设计者来说,保证所有不依赖于模板形参的名字在模板本身的作用于中定义。
对于模板的使用者来说,保证与模板形参相关的函数、类型以及声明等可见。
类模板内部的实例化机制:
在下面的类模板中,有:
template<class T>
class Item
{
int val;
Item*next ; //在类的内部可以使用非实例化的版本,因为编译器默认在类的内部引用类的名字时,使用的是同一版本。
};
template<class T>
class Queue
{
public:
Queue & operator=(const Queue &); //同上
….
private:
Item<T> *head; //此处必须类形参,因为编译器不会为类中使用的其他模板进行参数推断,因此需要自己指明。
Item<T>*tail;
};