Chinaunix首页 | 论坛 | 博客
  • 博客访问: 69968
  • 博文数量: 43
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 420
  • 用 户 组: 普通用户
  • 注册时间: 2014-06-27 15:04
个人简介

记录,分享

文章分类

全部博文(43)

文章存档

2017年(24)

2015年(1)

2014年(18)

我的朋友

分类: C/C++

2017-03-16 11:00:19

1.  模板定义

(1)函数模板
    模板定义以关键字template开始,后接模板形参表,模板形参表是用尖括号扩住的一个或多个模板形参,以逗号分隔。
template
int compare(const T &v1 , const T &v2) { ... }
    模板形参可以是表示类型的类型形参,也可以是表示常量表达式的非类型形参。类型形参跟在关键字class或者typename之后定义。
    使用函数模板时,编译器会推断哪个(或哪些)模板实参绑定到模板形参。一旦编译器确定了实际的模板实参,就称它实例化了函数模板的一个实例。(编译器实际承担了为每种类型而编写函数的单调工作)
    若要将函数模板声明为inline函数,inline关键字必须在模板形参表之后,返回类型之前.
template inline T min(const T &v1 , const T &v2);

(2)类模板

一个例子
template class Queue{
public :
  Queue();
  Type &front();
  const Type &front() const;
  void push(const TYpe &);
  void pop();
  bool empty() const ;
private :
  //TO DO
}

与调用函数模板相比,使用类模板时,必须为模板形参显式提供实参:
Queeu qi;

(3)模板形参

(4)模板类型形参
类型形参由关键字class或者typename后接形参名构成。 模板类型形参可作为类型说明符用在模板中的任何地方,与内置类型说明符或者类类型说明符的用法完全相同。

【1】typename与class的区别

在函数模板形参表中,typename和class具有相同的含义(使用typename更直观,因为可以使用内置类型而不仅仅是类类型作为实际的类型形参)。但是,class模板形参在标准c++之前就已经存在,若要兼容旧的程序就使用class。

【2】在模板内部定义指定类型
    除了定义数据成员或函数成员外,类还可以定义类型成员,比如size_type。如果要在函数模板内部使用这样的类型,必须告诉编译器我们正在使用的名字指的是一个类型。必须显式地这样做,因为编译器不能通过检查得知,有类型形参定义的名字何时是一个类型何时是一个值。

比如,
template
Parm fcn(Parm *array , U value){
  Parm::size_type *p;
}

    编译器并不知道size_type是Parm类定义的类型成员,还是static数据成员。如果是类型成员,则声明了一个指向Parm::size_type类型的指针,如果是数据成员,则执行了一次*操作符运算。默认情况下,编译器认为size_type是数据成员而非类型成员。
   如果希望编译器将size_type当做类型,必须显式地声明:
typename Parm::size_type *p; (Parm类型必须具有size_type成员)
通过在成员名前加上关键字typename作为前缀,可以告诉编译器将成员当做类型。
 

(5)非类型模板形参
    
    在调用函数模板时非类型形参将用值代替,值的类型在模板形参表中指定。例如,
template void array_init(T (¶m)[N]){ ...}

    模板非类型形参是模板定义内部的常量值,在需要常量表达式的时候,可使用非类型形参。当调用array_init时,编译器从数组实参计算非类型形参的值:
int x[42];
array_init(x);
char y[10];
array_init(y);

编译器将为array_init的每种调用分别实例化一个array_init版本。

    对模板的非类型形参而言,求值结果相同的表达式被认为是等价的。
int x[42];
array_init(x);
const int n = 10;
int y[32+n];
array_init(y);
两次array_init调用引用的是同一个array_init实例。

(6)编写泛型程序

    模板能否编译成功,一个因素是模板中使用的操作以及所用类型支持的操作。如果模板中使用了类型不支持的操作,编译将出错。
    编写泛型代码的两个重要原则:
a.  模板的形参是const引用
b.  函数体中的测试只用<比较
通过将形参设为const引用,就可以允许使用不支持复制的类型。

2.实例化

    产生模板的特定类型实例的过程称为实例化。

    类模板实参是必须的,而使用函数模板时,编译器通常会为我们推断模板实参。

(1)模板实参推断
    
    从函数实参确定模板实参的类型和值的过程叫做模板实参推断。

【1】多个类型形参的实参必须完全匹配

    函数模板类型形参可以作用于一个以上函数形参的类型。这种情形下,模板类型推断必须为每个对应的函数形参产生相同的模板实参类型,如果推断的类型不匹配,则调用出错。

template int compare(const T& v1 , const T& v2) { ... }
int main(){
  short si;
  compare(si,1024);
  return 0;
}

调用compare时,第一个实参推断出类型形参为short,第二个实参推断出类型形参为int,不匹配,编译失败。

    若希望函数模板允许实参的常规转换,必须用两个类型形参来定义:
template int compare(const T1& v1 , const T2& v2) { ... }

【2】类型形参的实参的受限转换

一般而言,不会转换实参以匹配已有的实例化,相反,会产生新的实例。除了产生新的实例化之外,编译器只会执行两种转换:
a.  const转换:接受const引用或const指针的函数可以分别用非const对象的引用或指针来调用,无需产生新的实例化。
b.  数组或函数到指针的转换。

【3】应用于非模板形参的常规转换

    类型转换的限制只适用于类型为模板形参的那些实参。用普通类型定义的函数形参可以使用常规转换。(区分模板形参和函数形参)

【4】模板实参推断与函数指针
    
    可以使用函数模板对函数指针进行初始化或赋值,此时,编译器使用指针的类型实例化具有适当模板实参的模板版本。

template int compare(const T& , const T&);
int (*pf1) (const int& , const int&) = compare;

pf1的类型是一个指向函数的指针,它的值是一个compare函数模板的一个实例的地址(函数的地址),该指针所指向函数的类型决定了compare的实例化。模板的类型形参被推断为int。

    如果不能从函数指针类型确定模板实参,编译就会出错。

(2)函数模板的显式实参

    某些情况下,不可能推断模板实参的类型。
    例如,计算两个不同类型的值之和的函数sum,我们希望返回类型能够足够大以适应两种类型之和。如果定义两个模板实参,那么任意一个作为返回值的类型都不符合要求。
    解决方式是引入第三个模板形参,它必须由调用者显式指定,即第三个模板形参不是由函数实参推断来的,而是由调用者显式传递模板类型实参。
template T1 sum(T2,T3);

    显式模板实参从左至右与对应模板形参相匹配,假如可以从模板形参推断,则只有结尾形参的显示模板实参可以省略:
如果sum声明如下
template T3 sum(T1,T2);
则必须为所有三个形参指定实参:
long v = sum(v1,v2);

注:将需要显示传递实参的模板形参放在模板形参表的前面

(3) 显式实参与函数模板的指针

    对于带有二义性的函数模板使用,可以通过显式实参避免二义性:
template int compare(const T&,constT&);
void func(int(*) (const string&,const string&));
void func(int(*) (const int&,const int&));
func(compare);

func的两个重载版本,参数都是指针,指向返回int型的函数,不同的是函数的参数。通过给compare传递模板实参int,实例化一个函数模板,作为参数传递给func,避免了二义性调用。


3. 模板编译模型

    当编译器看到模板定义的时候,它不立即产生代码,只有在看到模板调用时,才产生特定类型的模板实例。

    一般而言,调用函数时,编译器只需要看到函数的声明,类似地,定义类类型的对象时,类定义必须可见,但成员函数的定义不是必须可见的。而模板则不同:要进行实例化,编译器必须能够访问定义模板的源码。当调用函数模板或类模板的成员函数的是偶,编译器需要函数定义,需要那些通常放在源文件中的代码。

    两种编译模型(所有编译器都支持第一种,部分编译器支持第二种):
(1)包含编译模型 - 编译器必须看到用到的所有模板的定义。可以通过在声明函数模板或类模板的头文件中添加一条include指令(#include "xxx.cc")使定义可用。这一策略使我们能够保持头文件和实现文件分离,但是需要保证编译器在编译使用模板的代码时能够看到两种文件。
(2)分别编译模型:

4. 类模板成员

    通常,当使用类模板的名字的时候,必须指定模板形参,但这一规则有个例外:在类本身的作用域内部,可以使用类模板的非限定名。例如,在默认构造函数和复制构造函数的声明中,名字Queue是Queue的缩写表示,由编译器推断。
    编译器不会为类中使用的其他模板的模板形参进行这样的推断,必须使用完全限定名。P.547

(1)类模板成员函数

    类模板成员函数定义的形式:
a. 必须以关键字template开头
b. 必须指出它是哪个类的成员
c. 类名必须包含其模板形参
例子, template ret-type templ-name::member-name

    类模板的成员函数本身也是函数模板,需要使用类模板的成员函数产生该成员的实例化。与其他函数模板不同的是,在实例化类模板成员函数的时候,编译器不执行模板实参推断,相反,类模板成员的模板实参由调用该函数的对象的类型确定。由于不需要根据实参推断形参,因此类模板成员函数允许常规转换(普通函数模板不允许)

    类模板的成员函数只有为程序所用时才进行实例化,如果某成员函数从未使用,则不会实例化该成员函数。类模板的指针定义不会对相关类进行实例化,只有用到这样的指针时才会对类进行实例化。P.551

(2)非类型形参的模板实参
    
    非类型模板实参必须是编译时常量表达式

(3)类模板中的友元声明

5. 成员模板



阅读(391) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~