C++ Templates基础篇

  
概要
泛型程序设计的思想由来已久。Ada是最早支持泛型编程的程序语言。然后C++也加入了对泛型编程的支持。现在泛型编程思想已经被公认为是一种独立于OO的优秀的编程哲学。在C++中,泛型编程思想体现为一个基本概念,即模板。本文从理论和应用地角度简单地阐述C++模板的。
关键词 模板,C++ ,Templates,机制,应用
目录
1.      引言
1.1.1.       内部链接指针-两个具有相同名字但处于不同模块中的对象是两个完全不同的对象。
1.1.2.       外部(extern)链接指针-两个具有相同名字但处于不同模块中的对象是两个相同的对象。
1.1.3.       POI-C++编译器根据模板和特化/实例化参数生成唯一的强类型,这个强类型在客户端代码中的某个/某些位置,即实例化点(POI)。
1.1.4.       受限名称-被限定在某个特定的作用域内的C++名称,使用this->,::来标记。
1.1.5.       非受限名称-相对受限名称,不被限定在某个特定的作用域内。
1.1.6.       依赖型名称-依赖于特定参数的名称。
1.1.7.       非依赖型名称-依赖于特定参数的名称。
1.1.8.       Template-id-模板名称和紧跟在模板名称后面的一对尖括号内部的模板实参值所组成的实体,如std::vector<int>。
1.1.9.       翻译单元-编译器预处理器处理某个文件而获得的结果。
1.1.10.    声明:把一个C++名称引入或重新引入到你的程序的构造。一个声明也可以是一个定义,如名字空间和名字空间的命名、typedef、静态成员变量、全局变量、枚举。
1.1.11.    定义-引起C++编译器分配内存的行为构造。
泛型程序设计的思想由来已久。Ada是最早支持泛型编程的程序语言。然后C++也加入了对泛型编程的支持。现在泛型编程思想已经被公认为是一种独立于OO的优秀的编程哲学。在C++中,泛型编程思想体现为一个基本概念,即模板。而模板的意义在于:更大程度的复用;更好的性能;减少重复代码所带来的失误和维护量等等。
在C++中,模板的编译过程主要分为两个阶段:
         Step A. 词法/语法分析阶段。检查出错误的语法。
         Step B. Parse阶段。实例化模板,并检查模板代码,查看是否所有的调用都有效。
C++的模板主要分为两类,函数模板和类模板。函数模板抽象了对不同类型的同一性质的操作;类模板抽象了操作于不同类型的同一类实体。
函数模板为不同的模板实参定义了一个函数家族,当你传递模板实参的时候,可以根据实参的类型来对函数模板进行实例化。你也可以重载函数模板或对函数模板进行全局特化,以此对一些特殊的模板实参进行特殊的处理。C++是一门上下文相关的语言,因此一定要保证让函数模板的所有重载版本的声明都位于它们被调用的位置之前。
在一般情况下,模板的实例化都是由编译器自动完成的,这就是自动实例化或隐式实例化。在实例化的时候,C++编译器会根据特定的规则选出一些候选的函数,然后一个最好的匹配来实例化函数模板。
         函数模板的基本语法如下:
         template<typename T>
         void Ma(const T& a){…};
这里指的类,包括了C++中的class类型和union类型。和函数模板一样,类模板可以被全局实例化,不同于函数模板的是,函数模板不能被局部实例化,而类模板可以。类模板的实例化过程没有函数模板实例化那么复杂,C++编译器会根据特定的命名查找法,找到对应的类模板,然后实例化模板。与此同时,C++采用了一种延迟实例化的方式来实例化类模板中的成员,即只有那些被调用到的成员函数、静态常量等才会被实例化(注:所有的字段都需要被实例化)。
类模板的基本语法如下:
         template<typename T>
         class M{…}
函数模板或类模板并不能很好地处理所有的类型,因此在C++需要一种语言机制来支持对不同的模板参数进行特殊处理,这就是模板的特化。在C++中模板的特化可分为,全局特化和局部特化。类模板支持全局特化和局部特化。函数模板只支持全局特化。
全局特化就是将模板的所有参数替换为已知的类型或非类型。函数模板和类模板都能被全局特化。
函数模板的全局特化的基本语法如下:
         template< >
         void Ma(const int& a){…};
类模板的全局特化的基本语法如下:
         template<typename T>
         class M<int>{…}
局部特化就是将模板的部分参数替换为已知的类型或非类型。类模板能被局部特化,但函数模板不支持局部特化。
类模板的全局特化的基本语法如下:
         template<typename T>
         class M<int,T>{…}
模板的参数可分为形参和实参数。形参为声明或定义模板使用的模板参数,如T;实参为实例化模板时提供的模板参数,如int,在C++中,不能把局部类和局部枚举作为类型实参。
在一般情况下,编译器能够根据实参自动地演绎出形参。
模板形参同普通函数一样,可以有默认值。但是在C++中,只能为类模板指定默认参数,函数模板不支持默认参数。
按照参数的类型的不同,模板的形参可分为类型参数和非类型参数。
类型参数指的是模板的形参为某个特定的类型。
对于函数模板和类模板,模板参数并不一定局限于类型,普通值也可以作为模板参数。这些参数就是非类型参数。在C++中,非类型参数有如下限制:
1.         通常而言,非类型模板参数可以是常整数(包括枚举值)或指向外部链接的指针
2.         浮点数/空指针常量/字符串不能作为非类型模板参数
3.         类对象不能作为非类型模板参数
4.         整型可以作为非类型模板参数
5.         指向外部链接的指针类型可以作为非类型模板参数
6.         引用类型可以作为非类型模板参数
7.         函数/数组类型可以作为非类型模板参数
C++编译器将定义好的模板转化为特定的强类型的过程,称为模板实例化。其实例化的基本机制可简单地阐述为:根据相应的模板实体,适当地替换模板参数,从而获得一个普通类或函数。
按照实例化的方式的不同,模板的实例化可分为显式实例化和隐式实例化。
通过预先声明的方式显示实例化模板。在显示实例化中, 类模板特化的所有成员都被实例化了。它被当作提高编译效率的一条途径,显示实例化类模板或函数模板可以精确地控制模板实例的位置,但是灵活性不够。因此在实际的应用中,它很少被使用。
也被称为On-demand实例化或自动实例化或延迟实例化。相对显示实例化,自动实例化类模板或函数模板不能够精确控制模板实例的位置,但灵活性高。
当隐式实例化类模板时,同时也实例化了该模板的每个成员的声明,但没有实例化成员的定义,除了union类型成员。
隐式实例化机制称为两阶段查找:
Step 1. 模板的解析阶段。使用普通查找规则或ADL查找非依赖型名称,也查找非受限的依赖型名称。
Step 2. 模板的实例化阶段。确定实例化点(POI),并第二次查找非受限的依赖型名称和ADL
目前主要的实现方式有下面三种:
1.         贪婪实例化
         应用最广泛。特定的实体可以在多个目标文件和程序库中多次出现;链接时,只保留其中的一个。
         优点:保留了源对象之间的原始依赖型。
缺点:时间开销大;不能察觉细微差异。
2.         询问实例化     
         所有的翻译单元共享一个程序数据库,程序数据库纪录了生成信息。
         优点:支持并行编译。
3.         迭代实例化
1)         不实例化任何所需的可链接特化,直接编译源代码。
2)         使用预连接器链接目标文件
3)         预连接器调用连接器,丙烯解析它的错误信息,从而确认结果是否缺少某个实例化体。如果缺少的话,预连接器会调用编译器,来编译包含所需模板定义的源代码,然后(可选地)生成这个缺少的实例化体。
4)         重复第三步,直到不再生成新的定义。
缺点:时间消耗大;把诊断信息(错误和警告 )延迟到了连接器。
因为C++是一门上下文相关的语言,所以C++编译器需要根据语法单元的上下文来推断语法单元的语义。这就涉及到C++中的名称查找,即根据特定的名称(如类模板或函数模板的名称)来查找最佳匹配或最合适的定义(如最佳匹配的类模板和函数模板)。
在同一个作用域内不能有两个相同的名字,除了函数模板重载。
C++的名称查找可分为受限名称和非受限名称查找。
只在一个受限的作用域内(如某个类、函数等)进行查找。如this->count,在this所在类中查找count。
相对受限名称的查找,非受限名称的查找相对复杂。按照在特定的上下文中,查找的方式的不同,可简单地分为简单查找、依赖于参数的查找(ADL)。
1.         普通查找。
由内到外在所有外围类中逐层进行查找,但在某个类内部定义的成员函数定义中,它会先查找该类和基类的作用域,然后才查找外围类的作用域。
2.         依赖于参数的查找(ADL)。
也称Koeing查找。ADL先根据参数的上下文构造出associated class和associated namespace,然后在associated class和associated namespace进行名称的查找。
构造给定类型的associated class和associated namespace:
n 对于基本类型,该集合为空集。
n 对于指针和数组类型,该集合是所引用的类型的associated class和associated namespace。
n 对于枚举类型,associated namespace为枚举所在的namespace。
n 对于类成员, associated class为成员所在的类。
n 对于类,associated class为类本身、基类、外围类型;associated namespace为associated class所在的namespace的并集。
n 对于函数类型,该集合包括所有参数类型和返回类型的associated namespace和associated class。
n 类X的成员指针的类型,出了包括成员和类X的associated namespace和associated class。
通过在模板名称后面添加一对尖括号来声明和定义模板,这样的模板称为基本模板。基本模板常常用于模板的特化中。
如template<typename T> Length;
   template< > Length<int>{…};
         template< > Length< float >{…};
访问依赖于模板参数的类型名称。否则,会被编译器误认为是类模板中的静态量,如typename std::vector<T>::const_iterator iterator;
对于具有基类的类模板,自身使用名称X,并不一定等同于this->X。即使该X是从基类继承获得的,也是如此。需要显示地使用this->X或Base<T>::X。
T=T()。其中的T必须为基类型或被定义了默认构造函数。
对于 非引用类型参数,在实参的演绎过程中,会出现数组到指针的转换(Decay)。克服办法:
1.         使用非引用类型的参数取代引用参数。
2.         进行重载,编写接受引用和非引用参数的两个函数重载(比较好、普遍被采用的方式,然而可能会导致二义性)。
3.         特化或局部特化。
4.         重载数组类型。
5.         强制要求程序员进行显示类型转换
使用包含模型而不是分离模型。
1.         包含模型,即将模板的声明和定义都放到.h文件中,目前被广泛地应用于各个编译器中。
2.         分离模型,即模板的声明放到.h文件中,定义放到.c中,使得一个翻译单元可以引用函数模板或类模板,即便这个模板的定义在其他的翻译单元中。它的缺点是:耦合了模板实例化的翻译单元和模板定义的翻译单元;行为不可预知
即将那些基本的头文件放到预编译头文件中,不要将那些经常变化的头文件放到预编译头文件中。因为模板的编译速度比较慢,这样作可以提高编译速度。
附录A 一处定义原则(ODR)
对于同一个程序,非内联函数只能在所有的文件中定义一次;对于类和内联函数,每个翻译单元最多只能被定义一次。ODR可简单地分为程序范围的一次定义约束、翻译单元的一次定义约束以及跨翻译单元的等价性约束。
在整个程序的范围内,有仅有一次定义,这些定义包括:
n 非内联和非内联成员函数。
n 具有外部链接(extern)的变量。
n 静态成员变量。
n 类模板的静态成员变量。
n 非内联的函数模板和成员函数模板和类模板的非内联成员函数。
         在一个翻译单元内,实体不能被定义两次或两次以上。
对于能够在多个翻译单元中定义某种实体的能力,会带来某种潜在的新错误,如多个定义不匹配。
1.         C++ Templates中文版/(美)Vandevoorde,D.),(德)Josuttis,N.M.)著;陈伟柱译.-北京:人民邮电出版社,2004-1
2.         STL之父A.Stepanov专访/Graziano Lo Russo, Edizioni Infomedia srl著;荣耀 译
3.         STL源码剖析/候捷著;-武汉:华中科技大学出版社,2002.6.
4.         C++ Template Metaprogramming: Concepts, Tools, and Techniques from Boost and Beyond. By David Abrahams, Aleksey Gurtovoy; Addison Wesley Professional, December 10, 2004
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值