类模板详解

来源:互联网 发布:淘宝日系男装店铺推荐 编辑:程序博客网 时间:2024/04/27 01:21

模板类以下面的代码开头:

 template <class Type>

可以使用关键字typename代替class:
template <typename Type> //newer choice

 

可以使用自己的通用类型名代替Type,其命名规则与其他标识符相同。

 

模板还可以包含多个类型参数
template<class T1,class T2>

 

    通用类型标识符——例如这里的Type——称为类型参数(type parameter),这意味着它们类似于变量,但赋给它们的不能是数字,而只能是类型。

 

假设有类stack:
typedef unsigned long Item;

class stack
{private:
    enum{MAX=10};
    Item items[MAX];
    ...
 public:
    bool push(const Item & item);
    ...
};

 

建立模板类时,应将声明中标识Item的所有typedef替换为Type:
Item items[MAX];
bool push(const Item & item);

改为:
Type items[MAX];
bool push(const Type & item);

 

在模板类中使用模板成员函数,每个函数头都将以相同的模板声明打头:
template <class Type>

 

并使用通用类型名Type替换typedef标识符Item。另外,还需将限定符从stack::改为stack<Type>::。例如:
bool stack::push(const Item & item){...}
改为:
template <class Type>
bool stack<Type>::push(const Type & item){...}

 

在类外面,即指定返回类型或使用作用域解析符时,必须使用完整的stack<Type>
(如果在类声明中定义了方法即内联定义,则可以省略模板前缀和类限定符)

 

    除非编译器实现了新的export关键字,否则将模板成员函数放置在一个独立的实现文件中将无法运行。因为模板不是函数,它们不能单独编译。模板必须与特定的模板实例化请求一起使用。为此,最简单的方法是将所有模板信息放在一个头文件中,并在要使用这些模板的文件中包含该头文件。

 

    如果编译器实现了export关键字,则可以将模板方法定义放在一个独立的文件中,条件是每个模板声明都以export开始:
export template <class Type>   // preface with export
class stack { ... };

 

然后按常规类的方式进行:
1.将模板类声明(包含关键字export)放在一个头文件中,并使用#include编译指令使程序能够使用这些声明。
2.将模板类的方法定义放在一个源代码文件中,在该文件中包含头文件,并使用工程文件(或其他等有效文件)使程序能够使用这些定义。

 

使用模板类
声明一个类型为模板类的对象,方法是使用所需的具体类型替换通用类型名:

stack<int> kernels;       // create a stack of ints
stack<string> colonels; // create a stack of string objects

 

    必须显示地提供所需的类型,这与常规的函数模板是不同的。可以将内置类型或类对象用作类模板的类型。但如果是指针,则需要对程序做重大修改,否则无法很好的工作。

 

数组模板范例和非类型参数

template <class T,int n>
class ArrayTP
{private: T ar[n];
  ... };

 

int指出n的类型为int,这种参数——指定特殊的类型而不是用作通用类型名,称为非类型(non-type)表达式(expression)参数。

 

ArrayTP<double,12> eggweights;


将导致编译器定义名为ArrayTP<double,12>的类,并创建一个类型为ArrayTP<double,12>的eggweights对象。定义类时,编译器将使用double替换T,使用12替换n。

 

    表达式参数可以是整形、枚举、引用或指针。模板代码不能修改参数的值,也不能使用参数的地址。所以,在ArrayTP模板中不能使用诸如n++和&n等表达式。在实例化模板时,用作表达式参数的值必须是常量表达式。

 

模板类可以用作基类,也可用作组件类,还可用作其他模板的类型参数。

 

例如,可以使用数组模板实现堆栈模板,也可以使用数组模板来构造数组——数组元素是基于堆栈模板的堆栈:
template<class T>
class Array { private: T entry; ... };

template<class Type>
class GrowArray: public Array<Type> {...}; //inheritance

template<class Tp>
class stack
{ Array<Tp> ar;        //use an Array<> as a component
  ... };

...
Array < stack<int> > asi;    // an array of stacks of int


(最后一条语句中,必须使用至少一个空白字符将两个>符号分开,以避免与>>操作符混淆)

 

递归使用模板


例如,对于前面的数组模板定义,可以这样使用:
ArrayTP< ArrayTP<int,5>,10> twodee;

 

这使得twodee是一个包含10个元素的数组,其中每个元素都是一个包含5个int元素的数组。与之等价的常规数组声明如下:
int twodee[10][5];


(在模板句法中,维的顺序与等价的二维数组相反)

 

默认类型模板参数


可以为类型参数提供默认值:
template<class T1,class T2 = int> class Topo {...};

 

如果省略T2的值,编译器将使用int:
Topo<double,double> m1;    //T1 is double,T2 is double
Topo<double> m2;           //T1 is double,T2 is int

 

(可以为类模板类型参数提供默认值,但不能为函数模板参数提供默认值。不过可以为非类型参数提供默认值,这对于类模板和函数模板都是适用的)

 

模板具体化

1.隐式实例化(implicit instantiation)

 

编译器使用通用模板提供的处方生成具体的类定义:
stack<int> stuff;    // implicit instantiation

 

编译器在需要对象之前,不会生出类的隐式实例化:
stack<int> *pt;             //a pointer,no object needed yet
pt = new stack<int>;    //now an object is needed


第二条语句导致编译器生出类定义,并根据该定义创建一个对象。

 

2.显示实例化(explicit instantiation)


当使用关键字template并指出所需类型来声明类时,编译器将生成类声明的显示实例化。声明必须位于模板定义所在的名称空间中。例如:
template class stack<int>; //generate stack<int> class

 

将stack<int>声明为一个类。虽然没有创建或提及类对象,编译器也将生成类声明(包括方法定义)。和隐式实例化一样,也将根据通用模板来生成具体化。

 

3.显示具体化(explicit specialization)

 

有时候,可能需要在为特殊类型实例化时,对模板进行修改,使其行为不同。这种情况下,可以创建显示具体化:
template<> class classname<specialized-type-name> {...};
早期的格式为:
class classname<specialized-type-name> {...};

 

<specialized-type-name>改为特定的类型即可。

 

当具体化模板和通用模板都与实例化请求匹配时,编译器将使用具体化版本。

 

4.部分具体化(partial specialization)

 

即部分限制模板的通用性,例如,可以给类型参数之一指定具体的类型:
// general temlpate
template <class T1,class T2> class Pair {...};
// specialization with T2 set to int
template <class T1> class pair<T1,int> {...};

 

关键字template后面的<>声明是没有被具体化的类型参数。因此,上诉第二个声明将T2具体化为int,但T1保存不变。如果指定所有的类型,则<>内将为空,这将导致显示具体化:
// specialization with T1 and T2 set to int
template <> class Pair<int,int> {...};

 

如果有多个模板可供选择,则编译器将使用具体化程度最高的模板:
Pair<double,double> p1;   //use general Pair template
Pair<double,int> p2;      //use Pair<T1,int> partial specialization
Pair<int,int> p3;         //use Pair<int,int> explicit specialization

 

也可以通过为指针提供特殊版本来部分具体化现有的模板:
template<class T>         //general version
class Feeb {...};
template<class T*>
        //pointer partial specialization
class Feeb {...};         //modified code

(如果提供的类型不是指针,则编译器将使用通用版本;如果提供的是指针,则编译器将使用指针具体化版本)

Feeb<char> fb1;       //use general Feeb template,T is char
Feeb<char *> fb2;     //use Feeb T* specialization,T is char
(如果没有进行部分具体化,则第二个声明将使用通用模板,将T转换为char *类型。如果进行了部分具体化,则第二个声明将使用具体化模板,将T转换为char。)

 

部分具体化特性使得能够设置各种限制:
//general template
template <class T1,class T2,class T3> class Trio{...};
//specialization with T3 set to T2
template <class T1,class T2> class Trio<T1,T2,T2> {...};
//specialization wih T3 and T2 set to T1*
template <class T1> class Trio<T1,T1*,T1*> {...};

根据上述声明,编译器将作出如下选择:
Trio<int,short,char *> t1;        // use general template
Trio<int,short> t2;               //use Trio<T1,T2,T2>
Trio<char,char *,char *> t3;      //use Trio<T1,T1*,T1*>

 

成员模板
模板类将另一个模板类和模板函数作为其成员:
template <typename T>
class beta
{ private:
     template <typename V>
//nested template class member
     class hold
     { ... };
      hold<T> q;
           //template object
      hold<int> n;         //template object
  public:
     beta(T t, int i): q(t),n(i) {}
     template<typename U>
     U blab(U u,T t) { return (n.value() + q.value() ) * u / t; }
     void show()const { q.show();n.show();}
};

 

如果编译器接受类外面的定义,则可如下编写代码:
template <typename T>
class beta
{private:
    template <typename V>
    // declaration
    class hold;
    hold<T> q;
    hold<int> n;
 public:
    beta(T t, int i): q(t),n(i) {}
    template<typename U>
     // declaration
    U blab(U u,T t);
    void show()const { q.show();n.show();}
};

 

//member definition
template <typename T>
     template <typename V>
     class beta<T>::hold
     { ... };

 

//member definition
template <typename T>
     template <typename V>
     U beta<T>::blab(U u,T t)
     {
       return (n.value() + q.value() ) * u / t;
     }

 

上述定义将T、V和U用作模板参数。因为模板是嵌套的,因此必须使用句法:
template <typename T>
     template <typename V>
而不是:
template <typename T,typename V>


定义还必须指出hold和blab是beta<T>类的成员,这是通过使用作用域解析操作符来完成的。

 

将模板用作参数

 


模板还可以包含本身就是模板的参数:
template <template <typename T> class Thing>
class Crab
{private:
    Thing<int> s1;
    Thing<double> s2;
 ... };

 


上述中模板参数是template <typename T> class Thing,其中template <typename T> class 是类型,Thing是参数。假设有下面的声明:
Crab<king> legs;

为使上述声明被接受,模板参数king必须是一个模板类,其声明与模板参数Thing的声明匹配:
template <typename T>
class king { ... };

 

Crab的声明声明了两个对象:
Thing<int> s1;
Thing<double> s2;

前面的legs声明将用king<int>替换Thing<int>,用king<double>替换Thing<double>。
简而言之,模板参数Thing将被替换为声明Crab对象时被用作模板参数的模板类型。

 

混合使用模板参数和常规参数,如:
template <template <typename T> class Thing,typename U,typenameV>
class Crab
{ private:
     Thing<U> s1;
     Thing<V> s2;
  ... };
成员s1和s2可以存储的数据类型为通用类型,而不是硬编码指定的类型。