我们不可能写出对所有类型都适合的模板,某些情况下,通用模板定义对于某个类型可能是完全错误的,这个时候我们需要编写比模板函数更有效率的函数,这就是模板特化。
思考一个例子,编写比较函数的泛型函数模板:
template
int compare(const T &v1, const T &v2)
{
if(v1 < v2) return -1;
if(v1 > v2) return 1;
return 0;
}
当类型实参是C风格字符串的时候函数调用是有问题的。
声明:
template <>
int compare (const char* const&, const char* const &);
如果可以从函数形参表中推断模板实参, 则不必显示之初模板实参
template <>
int compare(const char* const&, const char* const &);
但是如果省去了空的模板形参表 template <> 这一行, 那就完全不一样的意义了, 变成了重载函数的声明
// generic template definition
template
int compare(const T& t1, const T& t2)
{
...
}
int compare(const char* const&, const char* const &);
上面代码声明中, 最上面为模板函数, 而下面则为一个普通的重载函数的声明.
需要注意的是, 对于普通重载函数, 传入函数的实参可以应用实参转换, 而对与特化模板函数, 对实参不应用转换, 实参类型必须与特化版本的形参类型完全匹配.
对具有同一模板实参集的同一模板, 不允许既有显示特化又有通用泛型模板实例化.
// generic template definition
template
int compare(const T& t1, const T& t2){ ... }
int main()
{
int i = compare("hello", "world");
}
int compare(const char* const&, const char* const &)
{ ... }
上面这个compare函数的调用就会报错, 因为在声明特化函数之前, 进行了与特化函数匹配的一次实例化.
考虑类似于vector的一个类, 应用到C风格的字符串上时, push_back只会复制指针, 不会复制字符串, 这样的代码会出现严重的内存问题.
这里以自定义的Queue类为例, 为C风格的字符串的Queue提供正确行为, 我们给类Queue特化一个const char*版本:
template <>
class Queue
{
public:
// use default constructor
// no copy control, use sythesized version for this class
void push(const char* val)
{
real_queue.push(val);
}
void pop()
{
real_queue.pop();
}
bool empty() const
{
return real_queue.empty();
}
// return string, not const char*
std::string front()
{
return real_queue.front();
}
private:
// forward call for generic string queue
Queue real_queue;
}
这是一个非常好的特化类的实现, Queue类的这个版本没有定义构造函数和复制控制构造函数, 因为它唯一的数据成员是类成员, 能够在被复制、复制、销毁时保证正确的工作. 包括push函数也只是直接调用string实例化的Queue类型对象, 将C风格字符串隐式转换为string类型来处理, front函数也是直接返回string类型, 避免对字符串数组/指针的管理. 值得一提的是,我们在特化类中尽可能的与普通泛型类模板保持一致的接口, 负责会让类的使用者感到非常迷惑.
当然,我们也可以选择只特化成员函数而不是去特化整个类, 来进一步简化我们的代码:
这里我们可以只特化push方法和pop方法, 实现如何复制字符串数组和释放该副本使用内存即可.