我们编写的模板很多时候并不是对所有的类型都是适合的,有时候甚至是错误的,这种情况下,我们需要模板特化(template specialization),模板特化就是指某个模板定义中的一个或多个模板形参的实际类型或实际值是指定的。
1.函数模板特化
函数模板特化形式:
- template关键字后接一对空尖括号<>
- 再接模板名和一对尖括号,尖括号中指定这个特化定义的模板形参
- 函数形参表
- 函数体
在C++模板与泛型(1.函数模板与类模板)中,我们提到了这样的compare函数:
template <typename T> int compare(const T &a, const T &b)
{
if(a < b) return -1;
if(b < a) return 1;
return 0;
}
但是,这个函数模板是不能用于C风格字符串的,因为如果传入两个C风格字符串,实际上传入的是两个字符串首字符的地址,比较的也是两个地址值得大小,这并不是我们想要的结果,所以,我们需要特化的compare函数模板:
template<> int compare<const char *>(const char* const &v1, const char* const &v2)
{
return strcmp(v1, v2);
}
这里的类型形参是const char ,所以函数形参是const char 的const引用。
我们还可以这样写这个函数:
int compare(const char* const &v1, const char* const &v2)
{
return strcmp(v1, v2);
}
这个时候我们就定义了compare函数的重载非模板版本。
2.类模板特化
在C++模板与泛型(2.编写自己的Queue类模板)中,我们编写了自己的Queue类模板,但同样,这个Queue类也不能用于C风格字符串,因为push函数在复制给定值创建新元素时,若传入的是C风格字符串,只会复制指针而不会复制字符,这种共享指针的情况很容易导致错误,尤其是如果指针指向动态内存时,可能会出现多次删除指针或误删的情况。为解决这个问题,我们可以特化整个类,也可以特化类的部分成员:
特化类
为const char*定义整个类的特化版本:
template<> class Queue<const char*>{
public:
...
void push(const char *);
void pop() { real_queue.pop(); }
bool empty() const { return real_queue.empty(); }
std::string front() { return real_queue.front(); }
const std::string &front() const { return real_queue.front(); }
private:
...
Queue<std::string> real_queue;
}
void Queue<const char*>::push(const char *val)
{
return real_queue.push(val);
}
这里我们给Queue添加了一个数据成员:string对象的Queue,通过调用real_queue的接口实现自己的成员。
在类外部定义特化成员时,成员之前不能加template<>标记。
特化成员
Queue类应用于C风格字符串时存在的主要问题就是字符串的复制,也就是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*> *p = new QueueItem<const char*>(new_item);
if(empty())
head = tail = p;
else{
tail->next = p;
tail = p;
}
++size_of_queue;
}
template<> void Queue<const char*>::pop()
{
QueueItem<const char*> *p = head;
delete head->item;
head = head->next;
delete p;
--size_of_queue;
}
注意,模板特化的声明必须在使用特化的每个文件中出现,所以模板特化声明应放在头文件中。