首页 > 代码库 > C++ 模板的编译 以及 类模板内部的实例化

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;

 

};