19.5 函数模板特化¶
By Alex on December 3rd, 2016 | last modified by Alex on January 23rd, 2020 | 翻译by dashjay 2020.07.10
当你实例化一个函数模板,使用给定的类型,编译器会复刻一份模板函数,并且用实际定义的要使用的参数类型替换模板类型参数。这意味着一个特定的函数将会在每个实例类有同样的实现细节(也就是说只是类型不同)。大多数情况下,这正是你想要的,偶尔也有一些情况下,在某些特殊类型的数据上实例化模板函数会有轻微的不同。
模板特化就是解决这个的方法之一。
让我们哪来看一个很简单的例子:
template <class T>
class Storage
{
private:
T m_value;
public:
Storage(T value)
{
m_value = value;
}
~Storage()
{
}
void print()
{
std::cout << m_value << '\n';
}
};
以上的代码可以在很多不同的数据类型下工作。
int main()
{
// Define some storage units
Storage<int> nValue(5);
Storage<double> dValue(6.7);
// Print out some values
nValue.print();
dValue.print();
}
这将产生结果
5
6.7
现在,假设说我们想要 double
类型(仅仅是双精浮点数)输出为特殊的科学计数法。为了达到这个目的,我们可以使用函数模板特化(有时也叫做显式函数模板特化)来为 double
创建一个特殊版本的 print()
函数。这相当简单:简单的定义特化的函数(如果函数是一个类型,在类定义外照做即可。)替换模板类为你想要为之重定义的特殊类型,这就是我们为doubles专门特化后的 print()
函数:
template <>
void Storage<double>::print()
{
std::cout << std::scientific << m_value << '\n';
}
当编译器实例化 Storage<double>::print()
,他会检查我们早就显式的定义了函数,并且它将会使用那个我们早就定义好的函数替换通用模板函数。
模板 <>
告诉编译器这是一个模板函数,并且没有模板参数(因为在这个例子里,我们已经显式的制定了所有参数)。一些编译器可能允许你省略这个,但是带上它更合适。
结果,我们再次运行之前的的程序,将会打印:
5
6.700000e+000
另一个例子
现在让我们来举另一个例子,这时候的函数特化是可以非常有用。思考如果我们尝试使用char*
来实例化我们的模板类 Storage
,将会发生什么?
int main()
{
// Dynamically allocate a temporary string
char *string = new char[40];
// Ask user for their name
std::cout << "Enter your name: ";
std::cin >> string;
// Store the name
Storage<char*> storage(string);
// Delete the temporary string
delete[] string;
// Print out our value
storage.print(); // This will print garbage
}
当结果出现时,代替本应该打印用户输入的 storage.print()
打印了垃圾(garbage)!发生了什么?
当 Storate
使用 char*
实例化时,Storage<char*>
的构造函数看起来就像这样:
template <>
Storage<char*>::Storage(char* value)
{
m_value = value;
}
换句话说,这就是做了一个指针赋值(浅拷贝)!结果,m_value
和string
指向相同的内存地址。当我们删除 string
在 main
中,我们删除了 m_value
指向的数据!因此,当我们打印这些值时,出现的就是一些垃圾。
幸运的是,我们可以使用模板特化修复这些问题。相比于做一个指针拷贝,我们应该像构造函数那样,对输入的字符串做一个完全拷贝。因此我们来为 char*
这个类写一个特化的构造函数,,专门来解决这个问题。
template <>
Storage<char*>::Storage(char* value)
{
// Figure out how long the string in value is
int length=0;
while (value[length] != '\0')
++length;
++length; // +1 to account for null terminator
// Allocate memory to hold the value string
m_value = new char[length];
// Copy the actual value string into the m_value memory we just allocated
for (int count=0; count < length; ++count)
m_value[count] = value[count];
}
现在当为 Strorage<char*>
给变量分配内存时,这个构造函数将会被使用来替代默认的那个。结果 m_value
将会收到它自己的那份 string
的拷贝。因此,当我们删除 string
时,m_value 将不会被影响。
然而,这个类现在有内存泄露当使用 char*
实例化时,因为 m_value
将不会被删除,当一个 Storage
变量离开作用于时。就像你猜的那样,这也可以被解决,通过特化 Storage<char*>
。
template <>
Storage<char*>::~Storage()
{
delete[] m_value;
}
现在当 Storate<char*>
离开作用于的时候,特化的构造函数分配的内存将会被特化的析构函数给删除。
以上的例子虽然全都用了成员函数,你仍然可以特化一个非成员模板函数以同样的方式。