功能模板专业化
在实例化给定类型的函数模板时,编译器会模板化模板化函数的副本,并将模板类型参数替换为变量声明中使用的实际类型。这意味着特定函数将具有每个实例类型的相同实现细节(仅使用不同类型)。虽然大多数情况下,这正是您想要的,但偶尔会出现对特定数据类型实现略有不同的模板化函数有用的情况。
模板专业化是实现这一目标的一种方法。
我们来看一个非常简单的模板类:
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()
{
// 定义一些存储单元
Storage<int> nValue(5);
Storage<double> dValue(6.7);
// 打印出一些值
nValue.print();
dValue.print();
}
这打印:
五
6.7
现在,假设我们想要用科学计数法输出双值(并且只有双值)。为此,我们可以使用函数模板特化(有时称为完整或显式函数模板特化)来为类型double创建print()函数的专用版本。这非常简单:只需定义专用函数(如果函数是成员函数,在类定义之外执行),将模板类型替换为您希望重新定义函数的特定类型。这是我们用于双精度打印的专用print()函数:
template <>
void Storage<double>::print()
{
std::cout << std::scientific << m_value << '\n';
}
当编译器实例化Storage :: print()时,它将看到我们已经明确定义了该函数,它将使用我们定义的那个而不是从通用模板类中输出一个版本。
template <>告诉编译器这是一个模板函数,但是没有模板参数(因为在这种情况下,我们明确指定了所有类型)。有些编译器可能允许你省略它,但包含它是正确的。
因此,当我们重新运行上述程序时,它将打印:
五
6.700000e + 000
另一个例子
现在让我们看一下模板特化可能有用的另一个例子。考虑如果我们尝试使用数据类型为char *的模板化Storage类会发生什么:
int main()
{
//动态分配临时字符串
char *string = new char[40];
// 询问用户的名字
std::cout << "Enter your name: ";
std::cin >> string;
// 存储名称
Storage<char*> storage(string);
// 删除临时字符串
delete[] string;
//打印出我们的值
storage.print(); // 这将打印垃圾
}
事实证明,storage.print()不是打印用户输入的名称,而是打印垃圾!这里发生了什么?
当为char *类型实例化Storage时,Storage <char *>的构造函数如下所示:
template <>
Storage<char*>::Storage(char* value)
{
m_value = value;
}
换句话说,这只是一个指针赋值(浅拷贝)!结果,m_value最终指向与字符串相同的内存位置。当我们删除main()中的字符串时,我们最终删除了m_value指向的值!因此,我们在尝试打印该值时会出现垃圾。
幸运的是,我们可以使用模板特化来解决这个问题。我们真的很喜欢构造函数来创建输入字符串的副本,而不是执行指针复制。因此,让我们为数据类型char *编写一个专门的构造函数,它完全按照以下方式执行:
template <>
Storage<char*>::Storage(char* value)
{
//弄清楚字符串的值有多长
int length=0;
while (value[length] != '\0')
++length;
++length; // +1代表空终止符
// 分配内存以保存值字符串
m_value = new char[length];
// 将实际值字符串复制到刚刚分配的m_value内存中
for (int count=0; count < length; ++count)
m_value[count] = value[count];
}
现在,当我们分配Storage <char *>类型的变量时,将使用此构造函数而不是默认构造函数。因此,m_value将收到自己的字符串副本。因此,当我们删除字符串时,m_value将不受影响。
但是,此类现在具有char *类型的内存泄漏,因为在存储时不会删除m_value变量超出范围。您可能已经猜到,这也可以通过专门化Storage <char *>析构函数来解决:
template <>
Storage<char*>::~Storage()
{
delete[] m_value;
}
现在,当Storage <char *>类型的变量超出范围时,专用构造函数中分配的内存将在专用析构函数中删除。
虽然上面的示例都使用了成员函数,但您也可以以相同的方式专门化非成员模板函数。