我们很难写出所有可能被实例化的类型都合适的模板。某些情况下,
通用模板定义对于某个类型可能是完全错误的,所以我们需要能够实现处
理某些特殊情况,特化的概念变是如此。compare函数和Queue类是这
个问题的很好例子。因为与C风格字符串一起使用时,他们都不能正确工作。
template <typename T>
int compare(const T &v1,const T &v2)
{
if(v1 < v2) return -1;
if(v2 < v1) return 1;
return 0;
}
如果用两个const char* 实参调用这个模板定义,函数将比较指针的
值。也就是比较两个指针在内存中的相对位置,却并没有说明与指针所指
数组的内容有关的任何事情。
为了能够将compare函数用于字符串,必须提供一个知道怎样比较C风
格字符串的特殊定义。这些就被称作是特化的,它对模板的用户而言是透
明的。
1. 函数模板的特化
特化形式:
- 关键字template后面接一对空的尖括号<>;
- 再接模板名和一对尖括号<>,尖括号中指定这个特化定义的模板参数:
- 函数形参表
- 函数体
template<>
int compare<const char*> (const char* const &v1,
const char* const &v2)
{
return strcmp(v1,v2);
}
特化的声明必须与 对应的模板相匹配。类型形参固定为const char*。
因此,函数形参是const char* 的const引用。当调用compare函数的时候,
传给它两个字符指针,编译器将调用特化版本。而不调用上面的泛型版本。
const char *cp1 = "world", *cp2 = "hi";
int i1, i2;
compare(cp1, cp2); //调用特化函数模板
compare(i1, i2); //调用泛型函数模板
注意:
* 函数模板特化时template<>不能省略,如果缺少结果是声明该函数的重载。
* 必须包含函数形参列表。如果可以从形参列表推断模板实参,则不必显示指
定模板实参。
* 如果程序由多个文件构成,模板特化的声明必须在使用该特化的每个文件
中出现。
2.类模板的特化
当使用C风格字符串时,Queue类具有 compare函数相似的问题。问题就处
在push函数中,该函数复制给定的值以创建Queue中的新元素。默认情况下,
复制C风格字符串只会复制指针,不会复制字符。而显然复制指针将出现一系
列的严重问题。为了解决复制C风格字符串的问题,需要为const char*定义
整个类的特化版本:
template<> class Queue<const char*>{
public:
void push(const char*);
void pop() {real_queue.pop();}
bool empty() const {return real_queue.front();}
//返回类型与模板参数类型不同
std::string front() {return real_queue.front();}
const std::string &front() const {return real_queue.front();
private :
Queue<std::string> real_queue;
};
给Queue一个新的数据元素,string对象的Queue。
在类的外部定义一个成员:
void Queue<const char*>::push (const char* val)
{
return real_queue.push(val);
}
这个函数通过调用read_queue的push函数把val指向的数组复制到未命名的
string 对象中。当需要出队列的时候调用相应real_queue.pop()函数即返
回了这个string,从而解决了不用复制指针的问题。
3.特化成员而不特化类
在上例的实现中,我们可以换一种方法,即不需要特化类,而只需要特化类
的成员函数push、pop。
根据函数模板特化的要求:
template <>
void Queue<const char*>::push(const char *const &val)
{
char * new_item = new char[strlen(val)+1];
strncpy(new_item, val, strlen(val)+1);
QueueItem<const char*> *pt =
new QueueItem<const char*>(new_item);
if(empty())
head = tail = pt; //队列中没有元素
eles{
tail->next = pt; //添加新元素到列尾
tail = pt;
}
}
template<>
void Queue<const char*>::pop()
{
QueueItem<const char*> *p = head;
delete head->item; //删除队首元素
head = head->next; //指向当前队首元素
delete p; //删除零时指针
}
4.类模板的部分特化
如果类模板有一个以上的模板形参,我们很有可能只要特化某些模板形参
而不是全部形参。这时我们就需要使用类的部分特化。
//定义模板类
template <class T1, class T2>
class some_template{
// ...
};
//定义模板类的部分特化:T2类型固定,部分特化T1类型
template<class T1>
class some_template<T1, int>{
// ...
};
//使用类模板的部分特化
some_template<int, string> foo; //使用模板类
some_template<string,int> bar; //使用模板类的部分特化
通过使用模板特化能解决一些在通常或者通用情况下无法解决的特殊问题。
在掌握了基本的语法规范和实现方法后便可以加以应用。