转自:http://blog.sina.com.cn/s/blog_93b45b0f01014jnj.html
模板与泛型编程是C++中最为复杂、最为变态,当然也是它区别于、优于别的语言的一个语言特点。这里介绍一下,有关模板特化与偏特化的相关概念。
在C++中泛型编程(不要和C++模板机制简单的等同起来)基本上享有和面向对象等同的地位,甚至比面向对象的地位更高。许多书上讲,泛型编程和面向对象一定程度上都有一定的多态性,这点我们就不引深了,把面向对象拉进来,是想引入几个泛型编程的概念。
(1)实例化(instantiation)
与类实例化为对象对应,模板做为一个非常抽象的东西,生成一个具体的东西的动作成为实例化。当然这种实例化的区别有很多,对于类实例化,是发生在运行的过程中,通过构造函数进行实例化;对于模板,则是在编译的过程中,编译器通过“类型推导”进行实例化。而运行的时候,系统根本不需要、也不知道模板的概念。(从这里我们也可以看出模板的一些好处,减轻编程负担,但不把这种负担转嫁给运行时复杂度(设计模式中的各种方法就有点这样的意思,而是将这些负担转嫁给编译器,所以C++是一种重编译语言,但是事实上,对于C++这样的语言,编译时间长短基本不影响整个软件的价值)
(2)特化(specialization)
“那些C++语言定义与标准化的专家们,永远比我们想的多,并且C++语言设计和发展的一大宗旨就是,“从来不限制程序员的发挥”,这成就了C++的巨大灵活性,当然随之而来的是巨大复杂性与危险性。大部分的C++程序是在限制使用C++各项特性的情况下开发出来的。如果“火力全开”,绝对有可能会一发不可收拾(当然也有可能成就一个经典的C++软件),这就是C++,选择它,你们就选择了一段探险历程,基本上你永远都在学习,而大部分的学习不是在学习如何让你的程序更高效,而是学习如何不掉进一个又一个的陷阱以及危险之中。”——徐勇泉
在模板的特化上,就表现出来C++的灵活性。
我们知道模板是为了让程序员能够脱离具体的数据类型开发出代码模板,在使用的时候有程序员提供具体的数据类型,然后由编译器进行实例化,这就要求所有的使用这个代码模板的数据类型所要跑的数据逻辑是一样的,当然通常我们就是希望这样(要不然我们就不需要模板了)。但是,10个数据类型,可能有一个数据类型,它对某个应用的逻辑过程要求就比较特别,进而不能使用通用的代码模板来进行限定,这个时候,你或许会说,“解决方法很简单,对于该特定的数据类型,我们不使用模板就是了,我们单独为它建立一个逻辑代码过程就可以了”。这是一个解决方法,但是之前你定义的模板是默认“可适应”该数据类型的,而软件开发过程中,你永远不要保证,在后来的程序员中(甚至是你自己)不会直接使用该模板,而不是特定的程序代码来完成特定的数据类型的操作。(庞大的软件开发的一个重要思想就是我们在较后期开发过程中,不强求对前面的所开发的东西牢记在心)。所以上面的解决方法不合理。
这就是模板特化的用处了。模板特化是指针对某个特定的类型,在定义的时候给出不同一般数据类型的逻辑实现。而在使用的时候,这个特殊性完全被屏蔽,你仍然只需要按照模板来使用,但是编译器会根据你之前的设定,给特别的数据类型以特定的代码逻辑。
(3)偏特化(partial specialization)
有了上面特化的基本概念,你会容易接受偏特化的思想。
偏特化,主要体现在,partial这个字上面,特化的意思是对某种数据类型“特殊化”的意思。而偏特化,自然就是对某种数据类型、或这说对这个模板“一点点的”、“不完整的”“特殊化”处理。
例如,你的模板类需要两个类型参数,而我们对第一类型参数为char类型的情况有特殊处理,这样不管第二个参数是什么类型,编译的时候所实例化的代码就是那个特殊的模板代码了。
再例如,如果你的模板需要一个类型参数(为了利于说明问题),但是,我们需要当类型为指针的情况下,需要有特殊的逻辑处理过程,例如,比较大小的时候,如果输入指针,则比较两个指针内容的大小是无意义的,这个特殊逻辑过程应该是比较两个指针指向的对象的大小而定的,这是一种常用的偏特化。
(4)有关特化与偏特化的语法说明:
1.引言
C++中的模板分为类模板和函数模板,虽然它引进到C++标准中的时间不是很长,但是却得到了广泛的应用,这一点在STL中有着充分的体现。目前,STL在C++社区中得到了广泛的关注、应用和研究。理解和掌握模板是学习、应用和研究以及扩充STL的基础。而STL模板实例中又充斥着大量的模板特化和偏特化。
2.模板的定义
(1) 类模板
定义一个栈的类模板,它可以用来容纳不同的数据类型
说明如下:
template <class T>
class stack {
private:
list* top;
public:
stack();
stack(const stack&);
~stack();
void push(T&);
T& pop();
//…
};
上述定义中,template告诉编译器这是一个模板,尖括号中的<class T >指明模板的参数,可以有一个或多个,具体实现时由用户指定,其中template <class T >中的关键字class可以用关键字typename来代替。
类模板的使用除了要在声明时指明模板参数外,其余均与普通的类相同,例如:
stack<int> int_stack;
stack<char> ch_stack;
stack<string> str_stack;
int_stack.push(10);
ch_stack.push(‘z’);
str_stack.push(“c++”);
(2) 函数模板
假设现在要定义一个max函数来返回同一类型(这种类型是允许比较的)两个值的最大者.
template<class T>
T mymax(const T& t1,const T& t2)
{ return t1 < t2 ? t2 : t1; }
template <class T>的意义与类模板定义中相同。
模板函数的使用与普通非模板函数使用相同,因为模板函数的参数可以从其传入参数中解析出来。例如:
int highest = mymax(5,10);
char c = mymax(‘a’, ’z’);
mymax(5,10)解析出模板函数参数为int, mymax(‘a’, ’z’)解析出模板函数的参数为char。
3.模板的特化
(1) 类模板特化
有时为了需要,针对特定的类型,需要对模板进行特化,也就是特殊处理.例如,stack类模板针对bool类型,因为实际上bool类型只需要一个二进制位,就可以对其进行存储,使用一个字或者一个字节都是浪费存储空间的.
template <class T>
class stack {};
template < >
class stack<bool> { //…// };
上述定义中template < >告诉编译器这是一个特化的模板。
(2) 函数模板的特化
看下面的例子
main()
{
int highest = mymax(5,10);
char c = mymax(‘a’, ’z’);
const char* p1 = “hello”;
const char* p2 = “world”;
const char* p = mymax(p1,p2);
}
前面两个mymax都能返回正确的结果.而第三个却不能,因为,此时mymax直接比较两个指针p1 和 p2 而不是其指向的内容.
针对这种情况,当mymax函数的参数类型为const char* 时,需要特化。
template <class T>
T mymax(const T t1, const T t2)
{
return t1 < t2 ? t2 : t1;
}
template <>
const char* mymax(const char* t1,const char* t2)
{
return (strcmp(t1,t2) < 0) ? t2 : t1;
}
现在mymax(p1,p2)能够返回正确的结果了。
4.模板的偏特化
模板的偏特化是指需要根据模板的某些但不是全部的参数进行特化
(1) 类模板的偏特化
例如c++标准库中的类vector的定义
template <class T, class Allocator>
class vector { // … // };
template <class Allocator>
class vector<bool, Allocator> { //…//};
这个偏特化的例子中,一个参数被绑定到bool类型,而另一个参数仍未绑定需要由用户指定。
(2) 函数模板的偏特化
严格的来说,函数模板并不支持偏特化,但由于可以对函数进行重载,所以可以达到类似于类模板偏特化的效果。
template <class T> void f(T); (a)
根据重载规则,对(a)进行重载
template < class T> void f(T*); (b)
如果将(a)称为基模板,那么(b)称为对基模板(a)的重载,而非对(a)的偏特化。C++的标准委员会仍在对下一个版本中是否允许函数模板的偏特化进行讨论。
5.模板特化时的匹配规则
(1) 类模板的匹配规则
最优化的优于次特化的,即模板参数最精确匹配的具有最高的优先权
例子:
template <class T> class vector{//…//}; // (a) 普通型
template <class T> class vector<T*>{//…//}; // (b) 对指针类型特化
template <> class vector <void*>{//…//}; // (c) 对void*进行特化
每个类型都可以用作普通型(a)的参数,但只有指针类型才能用作(b)的参数,而只有void*才能作为(c)的参数
(2) 函数模板的匹配规则
非模板函数具有最高的优先权。如果不存在匹配的非模板函数的话,那么最匹配的和最特化的函数具有高优先权
例子:
template <class T> void f(T); // (d)
template <class T> void f(int, T, double); // (e)
template <class T> void f(T*); // (f)
template <> void f<int> (int) ; // (g)
void f(double); // (h)
bool b;
int i;
double d;
f(b); // 以 T = bool 调用 (d)
f(i,42,d) // 以 T = int 调用(e)
f(&i) ; // 以 T = int* 调用(f)
f(d); // 调用(g)