C++ Template 基本概念
1.C++ 支持两种类型的模板:函数模板和类模板
这些模板的声明和普通类和普通函数的声明区别是在模板声明需要引入一个参数化语句: template<...parameters here...>.在所属外围类的外部定义的成员模板可以多个个参数化子句,一个子句用于改成员模板自身,一个子句用于外围类模板.子句的顺序是从最外围的类模板开始依次到达内部模板.此外联合体union模板也是允许的.
对于类模板,只有那些被调用的成员函数,才会产生这些函数的实例化代码.只有成员函数被使用的时候才会被实例化.
P.S:
要在两个靠在一起的尖括号之间留一个空格.
除了两种基本类型模板之外,还可以使用相似的符号来参数化一下三种声明:
1)类模板的成员函数的定义;
2)类模板的嵌套类成员的定义;
3)类模板静态数据成员的定义;
对于虚成员函数,成员函数模板不能被声明为虚函数,但是类模板的普通成员函数可以是虚函数.
模板的链接:
每个模板都必须有一个名字,而且在它所属的作用域下,该名字必须是唯一的.除非函数模板可以被重载.特别是,类模板不能和另外一个实体共享一个名称,这一点和class类型不同.
模板名字是由链接的,但不能具有C链接.通常,模板具有外链接.唯一的例外就是前面有static修饰符的名字空间作用域下的函数模板.
因为模板通常具有外连接,所以不能在函数内部声明模板.
基本模板:
没有在模板名称后面添加一对尖括号的声明即为基本模板.当声明局部特化的时候,声明的就是非基本模板.另外函数模板必须是非基本模板.
2.模板参数:
模板参数有3种:
1)类型参数;
2)非类型参数;
3)模板的模板参数;
P.S:在同一对尖括号内部,位于后面的模板参数声明可以引用前面的模板参数名称,但是前面的不能引用后面的.
1)类型参数:
类型参数是通过关键字typename或者class引入的.在模板声明内部,类型参数的作用类似于typedef名称.
2)非类型参数:
非类型参数表示的是:在编译器或者链接期可以确定的常值.这种参数的类型必须是如下的一种:
1`整形或者枚举类型;
2`指针类型(包含普通对象的指针类型,函数指针类型,指向成员的指针类型);
3`引用类型(指向对象或者指向函数的引用都是允许的);
所有其他类型均不允许作为非类型参数.函数和数组也可以被指定为非类型模板参数,但要把它们先隐式转化为指针类型.非类型模板参数的声明和变量的声明类似,但它们不能具有static,mutable等修饰符,只能具有const和volatile限定符.最后非类型模板参数只能是右值,它们不能被取地址,也不能被赋值.
非类型模板参数是有限制的.它们可以是常整数(包括枚举值)或者指向外部链接对象的指针.
3)模板的模板参数:
模板的模板参数是代表类模板的占位符.它的声明和类模板的声明类似,但是不能使用关键字struct和union,只能用class.
对于模板的模板参数而言,它的参数名称只能被自身其他参数的声明使用.
函数模板并不支持模板的模板参数.
3.缺省模板参数:
缺省模板参数不能依赖自身的参数,但是可以依赖于前面的参数.对于任一个模板参数,只有在之后的模板参数都提供了缺省实参的前提下,才能具有缺省模板实参.后面的缺省值通常是在同个模板声明中提供的,但也可以在前面模板的声明中提供.
4.模板实参:
模板实参是指:在实例化模板时,用类替换模板参数的值.可以通过如下几种机制类确定这些值:
1)显式模板实参:紧跟在模板名称后面,在一对尖括号内部的显式模板实参值.所组成的整个实体成为template-id;
2)注入式类名称:对于具有模板参数P1,P2...的类模板X,在它的作用域中,模板名称(X)等同于template-id;
3)缺省模板实参:即使所用的模板参数都有缺省值,但是尖括号是不能省略的;
4)实参演绎:对于不是显式指定的函数模板实参,可以在函数调用语句中,根据函数调用实参的类型来演绎出函数模板实参;
实参的演绎是不允许进行自动类型转换的.
1`函数模板实参:
对于函数模板的模板参数,可以显式指定它们,或者借助于模板的使用方式对它们进行实参演绎.但是,默写模板实参永远也得不到演绎的机会,所以最好把这些实参所对应的参数放在模板参数列表的开始处.从而可以显式指定这些参数,而其它参数仍然可以进行实参演绎.
由于函数模板可以被重载,所以对于函数模板而言,显式提供所有的实参并不足以标识每一个函数.在函数模板中,显式指定模板实参可能会试图构造一个无效的C++类型.
2`类型实参:
模板的类型实参是一些用来指定模板类型的参数值.大多数类型都可以被用作模板的类型实参.但又两种情况例外:
1)局部类和局部枚举(函数定义内部声明的类型)不能作为模板的类型实参;
2)未命名的class类型或者未命名的枚举类型不能作为模板的类型实参(但是通过typedef声明给出的未命名类和枚举是可以作为模板类型实参的);
通常而言,对于模板的类型实参,要求该类型替换模板参数之后获得的构造必须是有效的.
3`非类型实参:
非类型实参是那些替换非类型参数的值,这个值必须是以下几种的一种:
1)某一个具有正确类型的非类型模板参数;
2)一个编译器整形常量(或枚举值)。这只有在参数类型和值的类型能够进行匹配,或者值的类型可以隐式转换为参数类型的前提下,才是合法的.
3)前面有个单目运算符&的外部变量或者函数的名称.对于函数或者数组变量,&运算符可以省略.这类模板实参可以匹配指针类型的费类型参数.
4)对于引用类型的非类型模板参数,前面没有&运算符的外部函数也是可以取的.
5)一个指向成员的指针常量;
当实参匹配"指针类型或者引用类型的参数"时,用户定义的类型转换(构造函数和重载类型转换运算符)和由派生类到基类的类型转换,都是不会被考虑的;即使在其他的情况下,这些隐式类型转换是有效的,但是在这里是无效的.隐式类型转换的唯一应用只能是:给实参加上关键字const或者volatile.
模板实参的一个普遍约束是:在程序创建的时候,编译器或者链接器要能够确定实参的值.另外,有些常值不能作为有效的非类型实参.这些常值包括:
1)空指针常量;
2)浮点整形;
3)字符串;
4`模板的模板实参:
模板的模板实参必须是一个类模板,它本身具有参数,该参数必须精确匹配它"所替换的模板的模板参数"本身的参数.在匹配过程中,"模板的模板实参"的缺省模板实参将不会被考虑(如果模板的模板实参具有缺省实参,那么模板的实例化过程是会考虑模板的模板参数的缺省实参的).
5`实参的等价性:
当每个对应实参值都相等时,就称这两组模板实参是相等的.对于类型实参,typedef名称并不会对等价性产生影响;对于非类型的整形实参,进行比较的是实参的值.
P.S:
1)从成员函数模板产生的函数永远也不会改写一个虚函数;
2)从构造函数模板产生的构造函数一定不会是缺省值的拷贝构造函数.类似,从赋值运算符模板产生的赋值运算符也一定不会是一个拷贝赋值运算符.
5.友元:
友元类的声明不能是类的定义,因此友元类通常都不会出现问题.在引入模板之后,友元类声明的唯一变化只是:可以命名一个特定的类模板实例为友元.如果要把类模板的实例声明为其他类(或者类模板)的友元,该类模板在声明的地方必须是可见的.
1`友元函数:
通过确认紧跟在友元函数名称后面的是一对尖括号,可以把函数模板的实例声明为友元.尖括号可以包含模板实参,但也可以通过调用参数来演绎出实参.如果全部实参都能通过演绎获得的话,那么尖括号里面可以为空.不能在友元声明中定义一个模板实例,命名一个实例的友元声明是不能作为定义的.
如果名称和面没有紧跟一对尖括号,那么只有下面两种情况下是合法的:
1)如果名称不是受限的(即没有包含一个::的域运算符),那么该名称一定不是引用一个模板实例.如果在友元声明的地方,还看不到所匹配的非模板函数(即普通函数),那么这个友元声明就是函数的首次声明.于是,该声明可以是定义.
2)如果名称是受限的(即前面有::),那么该名称必须引用一个在此之前声明的函数或者函数模板.在匹配的过程中,匹配的函数要优先与匹配的函数模板.然而,这样的友元声明不能是定义.
如果需要在类模板里面声明友元函数,前面的这些规则仍然适用的,唯一的区别就是:可以使用模板参数来表示友元函数.
在模板内部定义的友元函数的类型定义中,必须包含类模板的模板参数.
由于函数的实体处于类定义的内部,所以这些函数是内联函数.因此,在两个不同的翻译单元中可以生成相同的函数.
2`友元模板:
和普通的友元的声明一样,只有在友元模板声明的是一个非受限的函数名称,并且后面没有紧跟尖括号的情况下,该友元模板声明才能成为定义.
友元模板的声明的只是基本模板和基本模板的成员,当进行这些声明之后,与该基本模板相对应的模板局部特化和显示特化都会被自动地看成友元.