解读C++编程中类模板的三种特化
1.类模板显式特化
为了进行特化,首先需要一个通用的版本,称主模板.主模板使用了标准库堆算法. 堆是一种线性化的树形结构,将一个值压入一个堆中,实际上等于将该值插入到一个树形结构中;将一个值从堆中取出就等于移除并返回堆中最大值.但在处理字符的指针时会碰钉子.堆将按照指针的值进行组织.我们可以提供一个显式特化版本解决此问题(例1)如果希望除了一个针对constchar*的Heap外,还希望提供一个针对char*的Heap;(例2)
//主模板 template<typenameT> classHeap { private: std::vector<T>h_; public: voidpush(constT&val); Tpop(); boolempty()const//const声明在末尾表示该函数不能修改类变量 { returnh_.empty(); } } template<typenameT> voidHeap<T>::push(constT&val) { h_.push_back(val); std::push_heap(h_.begin(),h_.end()); } template<typenameT> THead<T>::pop() { std::pop_head(h_.begin(),h_.end()); Ttmp(h_.back()); h_.pop_back(); returntmp; }
例1
//显示特化版本 /*********************************************** *可以看到模板参数列表是空的,其实这根本不是一个模 *板.因为没有指定任何模板参数.所以模板的显式特化又被 *称作"完全特化". *Heap<constchar*>完全特化,不会导致模板的实例化; *Heap<int>特化,会导致模板的实例化; *编译器根据主模板的声明来检查类模板特化. ***********************************************/ template<>//注意,无任何参数,当然,它本来就不是一个模板 classHead<constchar*> { private: std::vector<constchar*>h_; public: voidpush(constchar*pval); constchar*pop(); boolempty()const//const声明在末尾表示该函数不能修改类变量 { returnh_.empty(); } }; //再次提醒,Head<constchar*>不是一个模板 voidHeap<constchar*>::push(constchar*pval) { h_.push_back(pval); std::push_heap(h_.begin(),h_.end()); }
例2
/*********************************************** *C++没有要求显式特化的接口必须和主模板的接口完全 *匹配.如该例中,没有定义主模板的empty函数,并且自行增加 *了size和capitalize两个函数. *提醒:此例中不定义empty函数是不可取的,定义模板的 *显式特化和类的派生之间虽然不存在任何技术上的联系,但 *是用户依然可以参考类的派生的优点,让特化版本至少具有 *主模板的基本能力. ***********************************************/ template<>//注意,无任何参数,当然,它本来就不是一个模板 classHead<char*> { private: std::vector<char*>h_; public: voidpush(char*pval); char*pop(); //注意,此处没有提供empty函数哟!!! size_tsize()const; voidcapitalize(); };
2.模板局部特化
模板局部特化首先要声明的是,C++还不支持对函数模板的局部特化,所以此处我们只讨论类模板的局部特化.我们依然首先需要一个主模板.(参考类模板显式特化)自我理解:如果针对不能的指针定义不同的完全特化,岂不是太麻烦了,有没有更好的办法呢?那就是局部特化了.(例1)提示:局部特化它是一个模板.完全特化不是一样模板.
例1
/*********************************************** *局部特化 *和完全特化不同,这里的Heap参数类型只是被部分的确 *定为T*,而T是一个未指定的类型,这就是为什么说它是局部 *特化的原因; *当使用一个未经任何修饰的指针类型来实例化Heap时, *局部特化将优先于主模板; *当使用constchar*或char*(参考类模板显式特化)来 *实例化Heap时,此时完全特化又会优先于局部特化. *Heap<std::string>h1;主模板T是std::string *Heap<std::string*>h2;局部特化T是std:string *Heap<int**>h3;局部特化T是int* *Heap<char*>h4;完全特化T是char* *Heap<constint*>h5;局部特化T是constint *Heap<int(*)()>h6;局部特化T是int() ***********************************************/ template<typenameT> classHeap<T*>//注意这里 { private: std::vector<T*>h_; public: voidpush(constT*val); T*pop(); boolempty() { returnh_.empty(); } }; template<typenameT> voidHeap<T*>::push(constT*val) { //...... }
例2
/*********************************************** *有一点很微妙但很有用:主模板的完全特化或局部特化 *必须采用与主模板相同数量和类型的实参进行实例化,但它 *的模板的参数并不需要具有和主模板相同的形式. ***********************************************/ //定义一个模板,有三个模板参数,书写形式如下 template<typenameR,typenameA1,typenameA2> //注意,局部特化中,模板参数也是三个,但书写形式可不一样喽 classHeap<R(*)(A1,A2)> { //...... }; Heap<char*(*)(int,int)>h7;//R是char*,A1和A2是int //把char*(*)(int,int)想象成一个"指向有两个参数的非成员函数的指针" template<classC,typenameT> classHeap<TC::*> { //...... }; Heap<std::stringName::*>h8;//T是string,C是Name
尽管为何需要对这些东西使用Heap只是一个猜测,先知道有这么一用法吧!
3.类模板成员特化
虽然模板的特化和类的派生之间没有任何关系,但在特化模板的时候,不妨借鉴一下派生的精神.也就意味着一个完全特化或局部特化通常必须重新实现主模板具备的所有能力.
例:
//主模板 template<typenameT> classHeap { private: std::vector<T>h_; public: voidpush(constT&val); Tpop(); boolempty()const//const声明在末尾表示该函数不能修改类变量 { returnh_.empty(); } } //其实我们真正需要特化的是push和pop两个函数. //对比显式特化,它是通过主模板,再写一个模板显式特化版本类; //而这里只是对类模板成员进行了单独特化. template<> voidHeap<constchar*>::push(constchar*const&pval) { h_.push_back(pval); std::push_heap(h_.begin(),h_.end(),strLess); } template<> constchar*Heap<constchar*>::pop() { std:pop_heap(h_.begin(),h_end(),strLess); constchar*tmp=h_.back(); h_.pop_back(); returntmp; }
注意,这些函数的接口必须和"它们正在特化其成员"的模板的相应接口相匹配.如例1,就得和主模板的接口相匹配.而如果你是自己再定义的一个显式/局部特化版本类,就不需要匹配一致.(见显式特化和局部特化),最后指出两点:首先,除了成员函数外,其实成员也可以被显式特化,如静态成员和成员模板.其次,显式特化是为模板或模板成员提供定制版本的一种手段;而显式实例化仅仅是明确地告诉编译器去实例化一个成员.