首页 > 代码库 > 模板实例化

模板实例化

如果说上一篇博文《模板名称》是教人怎么写模板,那么这一篇就是教人怎么使用模板。

模板实例化的复杂性在于:对于产生自模板的实体,它们的定义已经不再局限于源代码中的单一位置。
一、理解两个概念
(1)实例化:实例化在C++中通常指“根据类型创建一个对象”,但是在模板里面,实例化是指使用具体值替换模板实参,从模板中产生普通类,函数或者成员函数过程
(2)特化:这个过程最后获得的试题就是我们所说的特化。
然而,实例化过程并不是特化的唯一方式,还有显式特化,通过引入一个template<>来实现,如下:
template<typename T1, typename T2>
class MyClass{};
 
template<>
class MyClass<std::string, float>{};
 
二、按需实例化(on-demand实例化)
如果(某个组件)期望知道模板特化的大小,或者访问该特化的成员,那么整个定义就需要位于作用域中。
比如显示的调用模板的成员,或者是包含隐式转换
(1)显示调用成员函数
template<typename T> class C;   //前置声明
C<int>* p = 0; //这里只需要声明就够了
 
template<typename T>
class C{
    public:
        void f();
};
 
void g(C<int>& c){
    c.f();                    //此处需要知道整个模板的定义,因为编译器要确定f()是不是可以被访问到
}
 
(2)隐式类型转换
C++重载规则要求:如果候选函数的参数是class类型,那么该类型所对应的类就必须可见
template <typename T>
class C{
public:
    C(int);  //单参数隐式类型转换
};
 
void candidate(C<double> const&); //①允许编译器实例化该重载函数,但不是必须的,在VS2013中,就没有实例化参数
void candidate(int){}                        //②
 
int main()
{
    candidate(42);         //编译器不会选择①处的声明,因为一个精确的匹配要优于显式转型所获得的匹配
    return 0;
 
三、延迟实例化
编译器只对确实需要的部分实例化。换句话说,编译器会延迟模板的实例化。
(1)当隐式实例化类模板时,同时也实例化了该模板的每一个成员函数的声明,但并没有实例化相应的定义。
但是有些情况是不会延迟的,如下:
①类模板里面包含有匿名的union,那么,匿名的union成员同时也被实例化,
②虚函数,作为实例化类模板的结果,许多编译器实现都会实例化虚函数的定义,因为“实现虚函数调用机制的内部结构”要求虚函数的定义作为链接实体的存在。
(2)实例化类模板与实例化缺省的函数调用实参是分开的。换句话说,只有函数确实使用了缺省的实参,才会实例化该实参,如果这个函数不使用缺省的实参,那么就不会实例化该缺省的实参,而是显式使用实参来实例化。
template<typename T>
class Safe{};
 
template<int N>
class Danger{
    typedef char Block[N];            //如果N<=0的话,将会出错
};
 
template<typename T, int N>
class Tricky{
public:
    virtual ~Tricky(){}    //虚函数,并提供了定义
    void no_body_here(Safe<T>=3);     //该缺省实参是可疑的,但没有被使用,不会被实例化,不会出错
    void inclass(){
        Danger<N>no_boom_yet;          //没有被使用,不会被实例化,不会出错
}
//void error(){ Danger<0> boom;}      //如果没有被注释,会被要求给出这个类Danger<0>的完整定义,
//而实例化Danger<0>会出错,即使没有被使用,也不会被实例化,但仍然能够引发一个错误
//该错误是在泛模板处理中产生的
//void unsafe(T(*p)[N]);        //如果 没有注释掉的话,此处实例化声明的时候会出错
T operator->();
//virtual Safe<T> suspect();     //虚函数,但是没有提供定义,所以会引发一个链接期的错误,
//如果不注释掉的话,链接器就会给出这类错误
struct Nested{
    Danger<N> pfew;      //因为没有使用该结构,所以此处的没有实例化
};
 

union{

 

    int align;

 

Safe<T> anonymous;

 
};
};
 
int main()
{
    Tricky<int, 0> ok;
}
三、C++实例化模型 
(1)两阶段查找
第一阶段:使用普通查找规则(在适当情况也会使用ADL)对模板进行解析,查找非依赖型名称。另外非受限的依赖型名称(诸如函数调用中的函数名称,因为其具有一个依赖型实参)也会在这个阶段查找,只不过查找不完全,在实例化模板的时候还会再次进行查找。
 
第二阶段:发生在模板被实例化的时候,我们也称此时发生的地点(或源代码的某个位置)为一个实例化点POI。依赖型受限名称就在此时查找。另外,非受限的依赖型名称在此阶段也会再次执行ADL查找.
 
(2)POI(实例化点)
①对于指向非类型(也就是函数╮( ̄▽ ̄")╭)特化的引用, C++把他的POI定义在“包含这个引用定义或声明之后的最近名字空间域”。
class MyInt{
    public:
    MyInt(int i);
};
 
MyInt operator - (MyInt const);
bool operator >(MyInt const&, MyInt const&);
typedef MyInt int;    //②
template <typename T>
void f(T i)   
{
    if(i>0){
        g(-i);   //①
}
}
//(1)
void g(Int)    //这就是那个定义或声明
{
//(2)
    f<Int>(42); //这就是那个引用
//(3)
}
//(4)这就是那个“之后最近的名字空间域”,函数f<int>的一个特化会出现在这里
 
【注意】①位置的名称g, 是非受限依赖型名称,因为他的参数是依赖型的, 所以会在第二阶段查找只是使用ADL就能够找到函数g(Int)其实也就是g(MyInt);如果将MyInt替换成int,即②处为typedef int Int,那么第二阶段的查找关联命名空间就会是空集,也就找不到函数g(Int)的声明和定义。
 
②对于产生自模板的类实例的引用,它的POI只能定义在“包含这个实例引用定义或声明之前的最近名字空间域”。
template<typename T>
class S{
     public:
        T m;
};
//(5) 这里就是S的POI
unsigned long h() //这就是那个定义或声明
{
    //(6)
    return (unsigned long) sizeof(S<int>);  //这就是那个实例引用
    //(7)
}
//(8) 
 
四、显式实例化  
为模板特化显式的生成POI是可行的,我们把这种特化的构造成为显式实例化指示符。从语法关键字上讲,它有关键字template和后面的特化声明组成,所声明的特化就是即将有实例化获得的特化。
 
template<typename T>
void f(T) throw(T){}
 
下面有4个有效的显式实例化实体
template void f<int>(int) throw(int);
template void f<>(int) throw(int); 
template void f(int) throw(int); //通过演绎获得
template void f(int); //异常规范也可以省略,如果没有省略,异常规范必须匹配相应的模板
 
C++规定: 同一个程序中,每一个特定的模板特化最多只能存在一处显式实例化。而且,如果摸个模板特化已经被显式实例化(使用template),那么就不能对其进行显式特殊化(使用template<>)。
 

模板实例化